| … | … | @@ -39,6 +39,16 @@ |
| 39 | 39 | |
| 40 | 40 | #ifdef ENABLE_DLSS |
| 41 | 41 | #include "drivers/streamline/streamline_context.h" |
| 42 | #elif defined(NGX_DLSS_ENABLED) |
| 43 | #define ENABLE_NGX_DLSS 1 |
| 44 | #include <dlfcn.h> |
| 45 | #include <string> |
| 46 | #include "core/io/dir_access.h" |
| 47 | #include "core/os/os.h" |
| 48 | #include "drivers/vulkan/godot_vulkan.h" |
| 49 | #include "nvsdk_ngx_vk.h" |
| 50 | #include "nvsdk_ngx_helpers_vk.h" |
| 51 | #include "nvsdk_ngx_helpers_dlssd_vk.h" |
| 42 | 52 | #endif |
| 43 | 53 | |
| 44 | 54 | using namespace RendererRD; |
| … | … | @@ -579,7 +589,526 @@ bool DLSSEffect::is_ready(DLSSContext *p_context) { |
| 579 | 589 | } |
| 580 | 590 | return true; |
| 581 | 591 | } |
| 592 | #elif defined(ENABLE_NGX_DLSS) |
| 593 | |
| 594 | // ============================================================================ |
| 595 | // NGX DLSS backend for Linux — bypasses Streamline, talks directly to NGX SDK |
| 596 | // ============================================================================ |
| 597 | |
| 598 | static bool g_ngx_initialized = false; |
| 599 | static NVSDK_NGX_Parameter *g_ngx_params = nullptr; |
| 600 | static bool g_ngx_dlss_rr_available = false; |
| 601 | static bool g_ngx_dlss_sr_available = false; |
| 602 | |
| 603 | namespace RendererRD { |
| 604 | class NGXDLSSContext : public DLSSContext { |
| 605 | public: |
| 606 | NVSDK_NGX_Handle *dlss_rr_handle = nullptr; |
| 607 | NVSDK_NGX_Handle *dlss_sr_handle = nullptr; |
| 608 | bool feature_created = false; |
| 609 | NVSDK_NGX_PerfQuality_Value perf_quality = NVSDK_NGX_PerfQuality_Value_Balanced; |
| 610 | uint32_t render_width = 0; |
| 611 | uint32_t render_height = 0; |
| 612 | uint32_t output_width = 0; |
| 613 | uint32_t output_height = 0; |
| 614 | |
| 615 | virtual ~NGXDLSSContext() { |
| 616 | if (dlss_rr_handle) { |
| 617 | NVSDK_NGX_VULKAN_ReleaseFeature(dlss_rr_handle); |
| 618 | dlss_rr_handle = nullptr; |
| 619 | } |
| 620 | if (dlss_sr_handle) { |
| 621 | NVSDK_NGX_VULKAN_ReleaseFeature(dlss_sr_handle); |
| 622 | dlss_sr_handle = nullptr; |
| 623 | } |
| 624 | } |
| 625 | }; |
| 626 | } // namespace RendererRD |
| 627 | |
| 628 | static void _ngx_log_callback(const char *message, NVSDK_NGX_Logging_Level level, NVSDK_NGX_Feature source) { |
| 629 | print_line(vformat("[NGX LOG] [%d] %s", (int)level, String(message))); |
| 630 | } |
| 631 | |
| 632 | static bool _ngx_ensure_init() { |
| 633 | if (g_ngx_initialized) { |
| 634 | return true; |
| 635 | } |
| 636 | |
| 637 | RenderingDevice *rd = RD::get_singleton(); |
| 638 | if (!rd) { |
| 639 | return false; |
| 640 | } |
| 641 | |
| 642 | VkInstance vk_instance = (VkInstance)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TOPMOST_OBJECT, RID(), 0); |
| 643 | VkPhysicalDevice vk_phys_device = (VkPhysicalDevice)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_PHYSICAL_DEVICE, RID(), 0); |
| 644 | VkDevice vk_device = (VkDevice)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_LOGICAL_DEVICE, RID(), 0); |
| 645 | |
| 646 | if (!vk_instance || !vk_phys_device || !vk_device) { |
| 647 | ERR_PRINT("[NGX DLSS] Failed to get Vulkan handles from Godot RenderingDevice"); |
| 648 | return false; |
| 649 | } |
| 650 | |
| 651 | print_line("[NGX DLSS] Vulkan handles acquired, initializing NGX..."); |
| 652 | |
| 653 | // Create the log/data directory |
| 654 | DirAccess::make_dir_absolute("/tmp/ngx_dlss_logs"); |
| 655 | |
| 656 | // Tell NGX where to find DLSS feature .so files (libnvidia-ngx-dlss*.so) |
| 657 | // Search order: |
| 658 | // 1) next to executable (shipped game: game.x86_64 + .so in same dir) |
| 659 | // 2) ../thirdparty/ngx/lib/linux_x86_64 (dev build: binary in bin/, .so at repo root) |
| 660 | String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); |
| 661 | String ngx_shipped = exe_dir; |
| 662 | String ngx_dev = exe_dir.path_join("../thirdparty/ngx/lib/linux_x86_64").simplify_path(); |
| 663 | |
| 664 | // Convert to wchar_t for NGX API |
| 665 | // On Linux wchar_t is 4 bytes (UTF-32), so widen char-by-char from UTF-8 |
| 666 | auto to_wstring = [](const String &s) -> std::wstring { |
| 667 | CharString utf8 = s.utf8(); |
| 668 | std::wstring result; |
| 669 | result.reserve(utf8.length()); |
| 670 | for (int i = 0; i < utf8.length(); i++) { |
| 671 | result.push_back((wchar_t)(unsigned char)utf8[i]); |
| 672 | } |
| 673 | return result; |
| 674 | }; |
| 675 | static thread_local std::wstring shipped_ws, dev_ws; |
| 676 | shipped_ws = to_wstring(ngx_shipped); |
| 677 | dev_ws = to_wstring(ngx_dev); |
| 678 | const wchar_t *ngx_search_paths[] = { |
| 679 | shipped_ws.c_str(), |
| 680 | dev_ws.c_str(), |
| 681 | }; |
| 682 | print_line(vformat("[NGX DLSS] Searching for DLSS .so in: %s, %s", ngx_shipped, ngx_dev)); |
| 683 | |
| 684 | NVSDK_NGX_FeatureCommonInfo feature_info = {}; |
| 685 | memset(&feature_info, 0, sizeof(feature_info)); |
| 686 | feature_info.PathListInfo.Path = ngx_search_paths; |
| 687 | feature_info.PathListInfo.Length = 2; |
| 688 | feature_info.LoggingInfo.MinimumLoggingLevel = NVSDK_NGX_LOGGING_LEVEL_VERBOSE; |
| 689 | feature_info.LoggingInfo.LoggingCallback = _ngx_log_callback; |
| 690 | feature_info.LoggingInfo.DisableOtherLoggingSinks = false; |
| 691 | |
| 692 | // Get the REAL Vulkan function pointers from the driver, not volk globals. |
| 693 | // volk defines vkGetInstanceProcAddr as a global variable (function pointer), |
| 694 | // but libnvsdk_ngx.a (statically linked) resolves the symbol to the variable |
| 695 | // address and tries to call it as code — crashing on the pointer bytes. |
| 696 | // dlsym gives us the actual driver function address. |
| 697 | void *vulkan_lib = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_NOLOAD); |
| 698 | if (!vulkan_lib) { |
| 699 | vulkan_lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_NOLOAD); |
| 700 | } |
| 701 | ERR_FAIL_COND_V_MSG(!vulkan_lib, false, "[NGX DLSS] Cannot find loaded libvulkan.so for proc addr"); |
| 702 | |
| 703 | PFN_vkGetInstanceProcAddr real_vkGetInstanceProcAddr = |
| 704 | (PFN_vkGetInstanceProcAddr)dlsym(vulkan_lib, "vkGetInstanceProcAddr"); |
| 705 | PFN_vkGetDeviceProcAddr real_vkGetDeviceProcAddr = |
| 706 | (PFN_vkGetDeviceProcAddr)dlsym(vulkan_lib, "vkGetDeviceProcAddr"); |
| 707 | dlclose(vulkan_lib); |
| 708 | |
| 709 | ERR_FAIL_COND_V_MSG(!real_vkGetInstanceProcAddr || !real_vkGetDeviceProcAddr, false, |
| 710 | "[NGX DLSS] dlsym failed for Vulkan proc addrs"); |
| 711 | |
| 712 | print_verbose(vformat("[NGX DLSS] Real vkGetInstanceProcAddr: %d, volk global: %d", |
| 713 | (uint64_t)real_vkGetInstanceProcAddr, (uint64_t)vkGetInstanceProcAddr)); |
| 714 | |
| 715 | // Use Init_with_ProjectID as the nvpro-samples reference does |
| 716 | NVSDK_NGX_Result result = NVSDK_NGX_VULKAN_Init_with_ProjectID( |
| 717 | "a0f57b54-1daf-4934-90ae-c4035c19df04", // Project ID (GUID format) |
| 718 | NVSDK_NGX_ENGINE_TYPE_CUSTOM, |
| 719 | "4.7.0", // Engine version |
| 720 | L"/tmp/ngx_dlss_logs", |
| 721 | vk_instance, vk_phys_device, vk_device, |
| 722 | real_vkGetInstanceProcAddr, real_vkGetDeviceProcAddr, |
| 723 | &feature_info, |
| 724 | NVSDK_NGX_Version_API); |
| 725 | |
| 726 | if (NVSDK_NGX_FAILED(result)) { |
| 727 | ERR_PRINT(vformat("[NGX DLSS] NVSDK_NGX_VULKAN_Init_Ext2 failed: 0x%x", (uint32_t)result)); |
| 728 | return false; |
| 729 | } |
| 730 | |
| 731 | // Query required extensions (for diagnostics) |
| 732 | { |
| 733 | unsigned int inst_count = 0, dev_count = 0; |
| 734 | const char **inst_exts = nullptr, **dev_exts = nullptr; |
| 735 | NVSDK_NGX_Result ext_result = NVSDK_NGX_VULKAN_RequiredExtensions(&inst_count, &inst_exts, &dev_count, &dev_exts); |
| 736 | if (NVSDK_NGX_SUCCEED(ext_result)) { |
| 737 | print_line(vformat("[NGX DLSS] Required instance extensions: %d", inst_count)); |
| 738 | for (unsigned int i = 0; i < inst_count; i++) { |
| 739 | print_line(vformat("[NGX DLSS] Instance: %s", String(inst_exts[i]))); |
| 740 | } |
| 741 | print_line(vformat("[NGX DLSS] Required device extensions: %d", dev_count)); |
| 742 | for (unsigned int i = 0; i < dev_count; i++) { |
| 743 | print_line(vformat("[NGX DLSS] Device: %s", String(dev_exts[i]))); |
| 744 | } |
| 745 | } else { |
| 746 | print_line(vformat("[NGX DLSS] RequiredExtensions query failed: 0x%x", (uint32_t)ext_result)); |
| 747 | } |
| 748 | } |
| 749 | |
| 750 | // Allocate parameters (needed for feature creation/evaluation) |
| 751 | result = NVSDK_NGX_VULKAN_AllocateParameters(&g_ngx_params); |
| 752 | if (NVSDK_NGX_FAILED(result)) { |
| 753 | ERR_PRINT(vformat("[NGX DLSS] AllocateParameters failed: 0x%x", (uint32_t)result)); |
| 754 | NVSDK_NGX_VULKAN_Shutdown1(nullptr); |
| 755 | return false; |
| 756 | } |
| 757 | |
| 758 | // Skip capability parameter checks — they're unreliable on Linux. |
| 759 | // NGX loaded both dlss and dlssd .so files (confirmed via nvngx.log). |
| 760 | // We'll try creating features at render time and handle errors there. |
| 761 | g_ngx_dlss_sr_available = true; |
| 762 | g_ngx_dlss_rr_available = true; |
| 763 | |
| 764 | g_ngx_initialized = true; |
| 765 | print_line("[NGX DLSS] Initialized successfully. Will attempt DLSS-RR then fall back to SR."); |
| 766 | |
| 767 | return true; |
| 768 | } |
| 769 | |
| 770 | static NVSDK_NGX_Resource_VK _ngx_make_resource(RID p_texture_rid, bool p_read_write) { |
| 771 | VkImageView vk_view = (VkImageView)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_VIEW, p_texture_rid); |
| 772 | VkImage vk_image = (VkImage)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE, p_texture_rid); |
| 773 | VkFormat vk_format = (VkFormat)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_DATA_FORMAT, p_texture_rid); |
| 774 | RD::TextureFormat tex_fmt = RD::get_singleton()->texture_get_format(p_texture_rid); |
| 775 | |
| 776 | VkImageSubresourceRange subresource = {}; |
| 777 | subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
| 778 | subresource.baseMipLevel = 0; |
| 779 | subresource.levelCount = tex_fmt.mipmaps; |
| 780 | subresource.baseArrayLayer = 0; |
| 781 | subresource.layerCount = tex_fmt.array_layers; |
| 782 | |
| 783 | return NVSDK_NGX_Create_ImageView_Resource_VK( |
| 784 | vk_view, vk_image, subresource, vk_format, |
| 785 | tex_fmt.width, tex_fmt.height, p_read_write); |
| 786 | } |
| 787 | |
| 788 | DLSSEffect::DLSSEffect() { |
| 789 | Vector<String> modes; |
| 790 | modes.push_back("\n"); |
| 791 | shaders.mvec_decode_shader.initialize(modes, ""); |
| 792 | shaders.mvec_decode_version = shaders.mvec_decode_shader.version_create(); |
| 793 | shaders.mvec_decode_pipeline = RD::get_singleton()->compute_pipeline_create(shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0)); |
| 794 | } |
| 795 | |
| 796 | DLSSEffect::~DLSSEffect() { |
| 797 | shaders.mvec_decode_shader.version_free(shaders.mvec_decode_version); |
| 798 | if (g_ngx_initialized) { |
| 799 | if (g_ngx_params) { |
| 800 | NVSDK_NGX_VULKAN_DestroyParameters(g_ngx_params); |
| 801 | g_ngx_params = nullptr; |
| 802 | } |
| 803 | NVSDK_NGX_VULKAN_Shutdown1(nullptr); |
| 804 | g_ngx_initialized = false; |
| 805 | } |
| 806 | } |
| 807 | |
| 808 | DLSSContext *DLSSEffect::create_context(Size2i p_internal_size, Size2i p_target_size) { |
| 809 | if (!_ngx_ensure_init()) { |
| 810 | ERR_PRINT("[NGX DLSS] Cannot create context — NGX init failed"); |
| 811 | return nullptr; |
| 812 | } |
| 813 | |
| 814 | RendererRD::NGXDLSSContext *ctx = memnew(RendererRD::NGXDLSSContext); |
| 815 | ctx->render_width = p_internal_size.width; |
| 816 | ctx->render_height = p_internal_size.height; |
| 817 | ctx->output_width = p_target_size.width; |
| 818 | ctx->output_height = p_target_size.height; |
| 819 | |
| 820 | // Determine quality mode based on render/output ratio |
| 821 | float ratio = (float)p_internal_size.width / (float)p_target_size.width; |
| 822 | if (ratio >= 0.95f) { |
| 823 | ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_DLAA; |
| 824 | } else if (ratio >= 0.66f) { |
| 825 | ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_MaxQuality; |
| 826 | } else if (ratio >= 0.5f) { |
| 827 | ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_Balanced; |
| 828 | } else if (ratio >= 0.33f) { |
| 829 | ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_MaxPerf; |
| 830 | } else { |
| 831 | ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_UltraPerformance; |
| 832 | } |
| 833 | |
| 834 | print_line(vformat("[NGX DLSS] Context created: %dx%d -> %dx%d (quality: %d)", |
| 835 | p_internal_size.width, p_internal_size.height, |
| 836 | p_target_size.width, p_target_size.height, (int)ctx->perf_quality)); |
| 837 | |
| 838 | return ctx; |
| 839 | } |
| 840 | |
| 841 | void DLSSEffect::upscale(const DLSSContext::Parameters &p_params) { |
| 842 | RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_params.context; |
| 843 | if (!ctx) { |
| 844 | return; |
| 845 | } |
| 846 | |
| 847 | // Delay warmup frames |
| 848 | if (ctx->delay > 0) { |
| 849 | --ctx->delay; |
| 850 | return; |
| 851 | } |
| 852 | |
| 853 | ctx->last_parameters = p_params; |
| 854 | ctx->last_effect = this; |
| 855 | |
| 856 | // Decode motion vectors (same as Streamline path) |
| 857 | { |
| 858 | RD::get_singleton()->draw_command_begin_label("Decode Invalid Motion Vectors"); |
| 859 | UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton(); |
| 860 | ERR_FAIL_NULL(uniform_set_cache); |
| 861 | MaterialStorage *material_storage = MaterialStorage::get_singleton(); |
| 862 | ERR_FAIL_NULL(material_storage); |
| 863 | |
| 864 | RD::Uniform u_velocity_image(RD::UNIFORM_TYPE_IMAGE, 0, p_params.velocity); |
| 865 | RD::Uniform u_depth_texture(RD::UNIFORM_TYPE_TEXTURE, 0, p_params.depth); |
| 866 | |
| 867 | RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); |
| 868 | RID shader = shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0); |
| 869 | ERR_FAIL_COND(shader.is_null()); |
| 870 | |
| 871 | RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, shaders.mvec_decode_pipeline); |
| 872 | RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_velocity_image), 0); |
| 873 | RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_depth_texture), 1); |
| 874 | |
| 875 | auto texture_format = RD::get_singleton()->texture_get_format(p_params.velocity); |
| 876 | |
| 877 | float push_constants[20]; |
| 878 | push_constants[0] = texture_format.width; |
| 879 | push_constants[1] = texture_format.height; |
| 880 | push_constants[2] = 0.0f; |
| 881 | push_constants[3] = 0.0f; |
| 882 | memcpy(push_constants + 4, &p_params.reprojection.columns[0].x, sizeof(float) * 16); |
| 883 | RD::get_singleton()->compute_list_set_push_constant(compute_list, push_constants, sizeof(push_constants)); |
| 884 | |
| 885 | RD::get_singleton()->compute_list_dispatch_threads(compute_list, texture_format.width, texture_format.height, 1); |
| 886 | RD::get_singleton()->compute_list_add_barrier(compute_list); |
| 887 | |
| 888 | RD::get_singleton()->compute_list_end(); |
| 889 | RD::get_singleton()->draw_command_end_label(); |
| 890 | } |
| 891 | |
| 892 | // Inject NGX DLSS into the render graph via callback |
| 893 | RD::CallbackResource res[8]; |
| 894 | int num_resources = 0; |
| 895 | res[num_resources++].rid = p_params.color; |
| 896 | res[num_resources++].rid = p_params.output; |
| 897 | res[num_resources++].rid = p_params.depth; |
| 898 | res[num_resources++].rid = p_params.velocity; |
| 899 | |
| 900 | if (p_params.dlss_rr) { |
| 901 | if (p_params.dlss_rr_diffuse_albedo.is_valid()) { |
| 902 | res[num_resources++].rid = p_params.dlss_rr_diffuse_albedo; |
| 903 | } |
| 904 | if (p_params.dlss_rr_specular_albedo.is_valid()) { |
| 905 | res[num_resources++].rid = p_params.dlss_rr_specular_albedo; |
| 906 | } |
| 907 | if (p_params.dlss_rr_normal_roughness.is_valid()) { |
| 908 | res[num_resources++].rid = p_params.dlss_rr_normal_roughness; |
| 909 | } |
| 910 | if (p_params.dlss_rr_specular_hit_dist.is_valid()) { |
| 911 | res[num_resources++].rid = p_params.dlss_rr_specular_hit_dist; |
| 912 | } |
| 913 | } |
| 914 | |
| 915 | for (int i = 0; i < num_resources; i++) { |
| 916 | res[i].usage = RD::CALLBACK_RESOURCE_USAGE_TEXTURE_SAMPLE; |
| 917 | } |
| 918 | RD::get_singleton()->driver_callback_add((RDD::DriverCallback)DLSSEffect::_upscale_internal_graph_callback, p_params.context, VectorView<RD::CallbackResource>(res, num_resources)); |
| 919 | } |
| 920 | |
| 921 | void DLSSEffect::_upscale_internal(RDD::CommandBufferID cmdid, const DLSSContext::Parameters &p_params) { |
| 922 | RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_params.context; |
| 923 | |
| 924 | VkCommandBuffer vk_cmd = (VkCommandBuffer)RD::get_singleton()->get_device_driver()->command_buffer_get_native_handle(cmdid); |
| 925 | VkDevice vk_device = (VkDevice)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_LOGICAL_DEVICE, RID(), 0); |
| 926 | |
| 927 | bool use_dlss_rr = p_params.dlss_rr && g_ngx_dlss_rr_available; |
| 928 | |
| 929 | // Create NGX feature on first use |
| 930 | if (!ctx->feature_created) { |
| 931 | NVSDK_NGX_Result result; |
| 932 | |
| 933 | // Try DLSS-RR (Ray Reconstruction / denoiser) first if requested |
| 934 | if (use_dlss_rr && g_ngx_dlss_rr_available) { |
| 935 | NVSDK_NGX_DLSSD_Create_Params dlssd_create = {}; |
| 936 | dlssd_create.InWidth = ctx->render_width; |
| 937 | dlssd_create.InHeight = ctx->render_height; |
| 938 | dlssd_create.InTargetWidth = ctx->output_width; |
| 939 | dlssd_create.InTargetHeight = ctx->output_height; |
| 940 | dlssd_create.InPerfQualityValue = ctx->perf_quality; |
| 941 | // No AutoExposure: Godot's auto_exposure handles it via tonemapping. |
| 942 | // Double-exposure (DLSS internal + Godot) causes progressive white blowout. |
| 943 | dlssd_create.InFeatureCreateFlags = |
| 944 | NVSDK_NGX_DLSS_Feature_Flags_IsHDR | |
| 945 | NVSDK_NGX_DLSS_Feature_Flags_MVLowRes | |
| 946 | NVSDK_NGX_DLSS_Feature_Flags_DepthInverted; |
| 947 | |
| 948 | result = NGX_VULKAN_CREATE_DLSSD_EXT1( |
| 949 | VK_NULL_HANDLE, vk_cmd, 1, 1, |
| 950 | &ctx->dlss_rr_handle, g_ngx_params, &dlssd_create); |
| 951 | |
| 952 | if (NVSDK_NGX_SUCCEED(result)) { |
| 953 | ctx->feature_created = true; |
| 954 | print_line("[NGX DLSS] DLSS-RR feature created successfully"); |
| 955 | } else { |
| 956 | WARN_PRINT(vformat("[NGX DLSS] DLSS-RR creation failed (0x%x), falling back to DLSS-SR", (uint32_t)result)); |
| 957 | ctx->dlss_rr_handle = nullptr; |
| 958 | use_dlss_rr = false; |
| 959 | } |
| 960 | } |
| 961 | |
| 962 | // Fall back to DLSS-SR (Super Resolution / upscaler only) |
| 963 | if (!ctx->feature_created && g_ngx_dlss_sr_available) { |
| 964 | NVSDK_NGX_DLSS_Create_Params dlss_create = {}; |
| 965 | dlss_create.Feature.InWidth = ctx->render_width; |
| 966 | dlss_create.Feature.InHeight = ctx->render_height; |
| 967 | dlss_create.Feature.InTargetWidth = ctx->output_width; |
| 968 | dlss_create.Feature.InTargetHeight = ctx->output_height; |
| 969 | dlss_create.Feature.InPerfQualityValue = ctx->perf_quality; |
| 970 | dlss_create.InFeatureCreateFlags = |
| 971 | NVSDK_NGX_DLSS_Feature_Flags_IsHDR | |
| 972 | NVSDK_NGX_DLSS_Feature_Flags_MVLowRes | |
| 973 | NVSDK_NGX_DLSS_Feature_Flags_DepthInverted; |
| 974 | |
| 975 | result = NGX_VULKAN_CREATE_DLSS_EXT( |
| 976 | vk_cmd, 1, 1, |
| 977 | &ctx->dlss_sr_handle, g_ngx_params, &dlss_create); |
| 978 | |
| 979 | if (NVSDK_NGX_FAILED(result)) { |
| 980 | ERR_PRINT(vformat("[NGX DLSS] DLSS-SR feature creation failed: 0x%x", (uint32_t)result)); |
| 981 | return; |
| 982 | } |
| 983 | ctx->feature_created = true; |
| 984 | use_dlss_rr = false; |
| 985 | print_line("[NGX DLSS] DLSS-SR feature created successfully"); |
| 986 | } |
| 987 | |
| 988 | if (!ctx->feature_created) { |
| 989 | return; |
| 990 | } |
| 991 | } |
| 992 | |
| 993 | // Evaluate DLSS |
| 994 | if (use_dlss_rr && ctx->dlss_rr_handle) { |
| 995 | // DLSS Ray Reconstruction evaluation |
| 996 | NVSDK_NGX_Resource_VK color_res = _ngx_make_resource(p_params.color, false); |
| 997 | NVSDK_NGX_Resource_VK output_res = _ngx_make_resource(p_params.output, true); |
| 998 | NVSDK_NGX_Resource_VK depth_res = _ngx_make_resource(p_params.depth, false); |
| 999 | NVSDK_NGX_Resource_VK mvec_res = _ngx_make_resource(p_params.velocity, false); |
| 1000 | |
| 1001 | NVSDK_NGX_VK_DLSSD_Eval_Params eval = {}; |
| 1002 | memset(&eval, 0, sizeof(eval)); |
| 1003 | eval.pInColor = &color_res; |
| 1004 | eval.pInOutput = &output_res; |
| 1005 | eval.pInDepth = &depth_res; |
| 1006 | eval.pInMotionVectors = &mvec_res; |
| 1007 | eval.InJitterOffsetX = p_params.jitter.x; |
| 1008 | eval.InJitterOffsetY = p_params.jitter.y; |
| 1009 | eval.InRenderSubrectDimensions.Width = ctx->render_width; |
| 1010 | eval.InRenderSubrectDimensions.Height = ctx->render_height; |
| 1011 | eval.InReset = p_params.reset_accumulation ? 1 : 0; |
| 1012 | // Motion vectors are in UV space (0-1), DLSS expects pixel space — scale by render resolution |
| 1013 | eval.InMVScaleX = (float)ctx->render_width; |
| 1014 | eval.InMVScaleY = (float)ctx->render_height; |
| 1015 | eval.InFrameTimeDeltaInMsec = p_params.delta_time * 1000.0f; |
| 1016 | |
| 1017 | // G-buffer inputs for DLSS-RR (stack-allocated, valid for duration of evaluate call) |
| 1018 | NVSDK_NGX_Resource_VK diffuse_res, specular_res, normal_res, hit_dist_res; |
| 1019 | if (p_params.dlss_rr_diffuse_albedo.is_valid()) { |
| 1020 | diffuse_res = _ngx_make_resource(p_params.dlss_rr_diffuse_albedo, false); |
| 1021 | eval.pInDiffuseAlbedo = &diffuse_res; |
| 1022 | } |
| 1023 | if (p_params.dlss_rr_specular_albedo.is_valid()) { |
| 1024 | specular_res = _ngx_make_resource(p_params.dlss_rr_specular_albedo, false); |
| 1025 | eval.pInSpecularAlbedo = &specular_res; |
| 1026 | } |
| 1027 | if (p_params.dlss_rr_normal_roughness.is_valid()) { |
| 1028 | normal_res = _ngx_make_resource(p_params.dlss_rr_normal_roughness, false); |
| 1029 | eval.pInNormals = &normal_res; |
| 1030 | eval.pInRoughness = &normal_res; // Packed mode: roughness in W channel of normals |
| 1031 | } |
| 1032 | if (p_params.dlss_rr_specular_hit_dist.is_valid()) { |
| 1033 | hit_dist_res = _ngx_make_resource(p_params.dlss_rr_specular_hit_dist, false); |
| 1034 | eval.pInSpecularHitDistance = &hit_dist_res; |
| 1035 | } |
| 1036 | |
| 1037 | // View/projection matrices for DLSS-RR |
| 1038 | Transform3D view_matrix = p_params.cam_transform.affine_inverse(); |
| 1039 | Projection view_proj(view_matrix); |
| 1040 | float world_to_view[16]; |
| 1041 | float view_to_clip[16]; |
| 1042 | for (int i = 0; i < 4; i++) { |
| 1043 | world_to_view[i * 4 + 0] = view_proj.columns[0][i]; |
| 1044 | world_to_view[i * 4 + 1] = view_proj.columns[1][i]; |
| 1045 | world_to_view[i * 4 + 2] = view_proj.columns[2][i]; |
| 1046 | world_to_view[i * 4 + 3] = view_proj.columns[3][i]; |
| 1047 | } |
| 1048 | for (int i = 0; i < 4; i++) { |
| 1049 | view_to_clip[i * 4 + 0] = p_params.cam_projection.columns[0][i]; |
| 1050 | view_to_clip[i * 4 + 1] = p_params.cam_projection.columns[1][i]; |
| 1051 | view_to_clip[i * 4 + 2] = p_params.cam_projection.columns[2][i]; |
| 1052 | view_to_clip[i * 4 + 3] = p_params.cam_projection.columns[3][i]; |
| 1053 | } |
| 1054 | eval.pInWorldToViewMatrix = world_to_view; |
| 1055 | eval.pInViewToClipMatrix = view_to_clip; |
| 1056 | |
| 1057 | NVSDK_NGX_Result result = NGX_VULKAN_EVALUATE_DLSSD_EXT(vk_cmd, ctx->dlss_rr_handle, g_ngx_params, &eval); |
| 1058 | |
| 1059 | if (NVSDK_NGX_FAILED(result)) { |
| 1060 | ERR_PRINT(vformat("[NGX DLSS] DLSS-RR evaluate failed: 0x%x", (uint32_t)result)); |
| 1061 | } |
| 1062 | } else if (ctx->dlss_sr_handle) { |
| 1063 | // DLSS Super Resolution evaluation |
| 1064 | NVSDK_NGX_Resource_VK color_res = _ngx_make_resource(p_params.color, false); |
| 1065 | NVSDK_NGX_Resource_VK output_res = _ngx_make_resource(p_params.output, true); |
| 1066 | NVSDK_NGX_Resource_VK depth_res = _ngx_make_resource(p_params.depth, false); |
| 1067 | NVSDK_NGX_Resource_VK mvec_res = _ngx_make_resource(p_params.velocity, false); |
| 1068 | |
| 1069 | NVSDK_NGX_VK_DLSS_Eval_Params eval = {}; |
| 1070 | memset(&eval, 0, sizeof(eval)); |
| 1071 | eval.Feature.pInColor = &color_res; |
| 1072 | eval.Feature.pInOutput = &output_res; |
| 1073 | eval.Feature.InSharpness = p_params.sharpness; |
| 1074 | eval.pInDepth = &depth_res; |
| 1075 | eval.pInMotionVectors = &mvec_res; |
| 1076 | eval.InJitterOffsetX = p_params.jitter.x; |
| 1077 | eval.InJitterOffsetY = p_params.jitter.y; |
| 1078 | eval.InRenderSubrectDimensions.Width = ctx->render_width; |
| 1079 | eval.InRenderSubrectDimensions.Height = ctx->render_height; |
| 1080 | eval.InReset = p_params.reset_accumulation ? 1 : 0; |
| 1081 | // Motion vectors are in UV space (0-1), DLSS expects pixel space — scale by render resolution |
| 1082 | eval.InMVScaleX = (float)ctx->render_width; |
| 1083 | eval.InMVScaleY = (float)ctx->render_height; |
| 1084 | |
| 1085 | NVSDK_NGX_Result result = NGX_VULKAN_EVALUATE_DLSS_EXT(vk_cmd, ctx->dlss_sr_handle, g_ngx_params, &eval); |
| 1086 | if (NVSDK_NGX_FAILED(result)) { |
| 1087 | ERR_PRINT(vformat("[NGX DLSS] DLSS-SR evaluate failed: 0x%x", (uint32_t)result)); |
| 1088 | } |
| 1089 | } |
| 1090 | } |
| 1091 | |
| 1092 | void RendererRD::DLSSEffect::_upscale_internal_graph_callback(RenderingDeviceDriver *p_driver, RDD::CommandBufferID p_command_buffer, void *p_userdata) { |
| 1093 | RendererRD::NGXDLSSContext *self = (RendererRD::NGXDLSSContext *)p_userdata; |
| 1094 | self->last_effect->_upscale_internal(p_command_buffer, self->last_parameters); |
| 1095 | } |
| 1096 | |
| 1097 | bool DLSSEffect::is_ready(DLSSContext *p_context) { |
| 1098 | RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_context; |
| 1099 | if (!ctx || !g_ngx_initialized) { |
| 1100 | return false; |
| 1101 | } |
| 1102 | if (ctx->delay > 0) { |
| 1103 | return false; |
| 1104 | } |
| 1105 | return true; |
| 1106 | } |
| 1107 | |
| 582 | 1108 | #else |
| 1109 | // ============================================================================ |
| 1110 | // No DLSS backend available — stub implementation |
| 1111 | // ============================================================================ |
| 583 | 1112 | DLSSEffect::DLSSEffect() {} |
| 584 | 1113 | DLSSEffect::~DLSSEffect() {} |
| 585 | 1114 | DLSSContext *DLSSEffect::create_context(Size2i p_internal_size, Size2i p_target_size) { |