/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "rendering/vulkan14/vulkan14dlss.h" #include "rendering/vulkan14/vulkan14commands.h" #include "rendering/vulkan14/vulkan14memory.h" #include "utilities/Globals.h" #include "utilities/utilities.h" #include "imgui/imgui.h" #if defined(WITH_DLSS) #include #include #include #endif #include #include #include #include #include #include #if defined(WITH_DLSS) && !defined(_WIN32) #include #endif namespace vk14 { #if defined(WITH_DLSS) namespace { // the identifier of the program for ngx (a project of an engine of its own): anything shaped like a guid char const *const project_id{"5b3e2f0a-8c4d-4e6b-9a71-2d0f6c8e1a37"}; char const *const engine_version{"1.0"}; // the modes upscaling from less than the full size, from the largest part of the window to the smallest NVSDK_NGX_PerfQuality_Value const upscaling_modes[]{NVSDK_NGX_PerfQuality_Value_MaxQuality, NVSDK_NGX_PerfQuality_Value_Balanced, NVSDK_NGX_PerfQuality_Value_MaxPerf, NVSDK_NGX_PerfQuality_Value_UltraPerformance}; char const *mode_name(int const Mode) { switch (Mode) { case NVSDK_NGX_PerfQuality_Value_MaxPerf: return "performance"; case NVSDK_NGX_PerfQuality_Value_Balanced: return "balanced"; case NVSDK_NGX_PerfQuality_Value_MaxQuality: return "quality"; case NVSDK_NGX_PerfQuality_Value_UltraPerformance: return "ultra performance"; case NVSDK_NGX_PerfQuality_Value_UltraQuality: return "ultra quality"; case NVSDK_NGX_PerfQuality_Value_DLAA: return "dlaa"; default: return "none"; } } char const *preset_name(int const Preset) { switch (Preset) { case NVSDK_NGX_DLSS_Hint_Render_Preset_J: return "j"; case NVSDK_NGX_DLSS_Hint_Render_Preset_K: return "k"; case NVSDK_NGX_DLSS_Hint_Render_Preset_L: return "l"; case NVSDK_NGX_DLSS_Hint_Render_Preset_M: return "m"; default: return "default"; } } // the code of a result of ngx with its text (the text is ascii) std::string result_text(NVSDK_NGX_Result const Result) { char code[16]; std::snprintf(code, sizeof(code), "0x%08x", static_cast(Result)); std::string text{code}; if (auto const *wide{GetNGXResultAsString(Result)}) { text += " ("; for (auto const *c{wide}; *c != 0; ++c) { text += (*c < 128 ? static_cast(*c) : '?'); } text += ")"; } return text; } // the directory of the program, where the libraries of the features are looked for first std::filesystem::path program_directory() { #if defined(_WIN32) wchar_t path[MAX_PATH]; auto const length{::GetModuleFileNameW(nullptr, path, MAX_PATH)}; if ((length == 0) || (length >= MAX_PATH)) { return {}; } return std::filesystem::path(path).parent_path(); #else char path[4096]; auto const length{::readlink("/proc/self/exe", path, sizeof(path) - 1)}; if (length <= 0) { return {}; } path[length] = '\0'; return std::filesystem::path(path).parent_path(); #endif } } // namespace struct dlss::state { // ngx: where it writes its logs, where it looks for the libraries besides the directory of the program std::wstring data_path; std::vector paths; std::vector path_list; NVSDK_NGX_FeatureCommonInfo common{}; VkInstance instance{VK_NULL_HANDLE}; VkPhysicalDevice physical{VK_NULL_HANDLE}; VkDevice device{VK_NULL_HANDLE}; bool initialized{false}; bool available{false}; // the parameters given to the feature, with the capabilities of the driver in them NVSDK_NGX_Parameter *parameters{nullptr}; // the feature: the one the frames ask for and the one made struct feature { glm::ivec2 render{0}; glm::ivec2 create{0}; // the size the feature is made for, at least the size it's given glm::ivec2 output{0}; int mode{-1}; int flags{0}; int preset{0}; bool operator==(feature const &Other) const { return (render == Other.render) && (create == Other.create) && (output == Other.output) && (mode == Other.mode) && (flags == Other.flags) && (preset == Other.preset); } }; feature wanted; feature made; NVSDK_NGX_Handle *handle{nullptr}; // the sizes planned at the start glm::ivec2 plan_render{0}; glm::ivec2 plan_create{0}; glm::ivec2 plan_output{0}; int plan_mode{-1}; // what the replay of the frame evaluates. written by the main thread while it records the frame, read by the replay: the // main thread waits for the replay of the frame before it records the next one struct resource { VkImage image{VK_NULL_HANDLE}; VkImageView view{VK_NULL_HANDLE}; VkFormat format{VK_FORMAT_UNDEFINED}; VkImageAspectFlags aspect{VK_IMAGE_ASPECT_COLOR_BIT}; std::uint32_t width{0}; std::uint32_t height{0}; bool valid{false}; }; resource colour, depth, motion, exposure, output; glm::vec2 jitter{0.f}; bool reset{false}; float frame_time{0.f}; // set by the replay, read by the main thread once it waited for it (sync()) bool failed{false}; std::string error; unsigned long long memory{0}; unsigned int evaluations{0}; // what the main thread shows: taken from the above by sync(), as the statistics are made while the next frame is replayed bool seen_failed{false}; bool error_told{false}; std::string seen_error; feature seen_feature; unsigned long long seen_memory{0}; // the frame generation: the most frames it makes between two, its own parameters (one map for each feature, as the guide of // the frame generation asks), the feature the frames ask for and the one made std::uint32_t generation_max{0}; NVSDK_NGX_Parameter *generation_parameters{nullptr}; struct generation_feature { glm::ivec2 output{0}; glm::ivec2 render{0}; VkFormat format{VK_FORMAT_UNDEFINED}; bool operator==(generation_feature const &Other) const { return (output == Other.output) && (render == Other.render) && (format == Other.format); } }; generation_feature generation_wanted; generation_feature generation_made; NVSDK_NGX_Handle *generation_handle{nullptr}; // what the replay of the frame evaluates, as the resources above resource backbuffer, hudless, generation_depth, generation_motion; std::vector generated; NVSDK_NGX_DLSSG_Opt_Eval_Params generation_options{}; // set by the replay, read by the main thread once it waited for it bool generation_failed{false}; std::string generation_error; bool generation_error_told{false}; bool seen_generation_failed{false}; std::uint64_t generations{0}; std::uint64_t seen_generations{0}; // the debug window: the offsets of the jitter turned the other way, the history dropped bool flip_x{false}; bool flip_y{false}; bool reset_wanted{false}; // the frame generation: the clip space of its matrices turned over (as the images it's given), its history dropped bool generation_clip_flip{false}; bool generation_reset_wanted{false}; NVSDK_NGX_FeatureDiscoveryInfo discovery(NVSDK_NGX_Feature const Feature) const { NVSDK_NGX_FeatureDiscoveryInfo info{}; info.SDKVersion = NVSDK_NGX_Version_API; info.FeatureID = Feature; info.Identifier.IdentifierType = NVSDK_NGX_Application_Identifier_Type_Project_Id; info.Identifier.v.ProjectDesc.ProjectId = project_id; info.Identifier.v.ProjectDesc.EngineType = NVSDK_NGX_ENGINE_TYPE_CUSTOM; info.Identifier.v.ProjectDesc.EngineVersion = engine_version; info.ApplicationDataPath = data_path.c_str(); info.FeatureInfo = &common; return info; } void prepare_paths() { auto logs{user_config_path("ngx")}; std::error_code error; if (false == logs.empty()) { std::filesystem::create_directories(logs, error); } data_path = ((logs.empty() || error) ? std::filesystem::path(".") : logs).wstring(); paths.clear(); path_list.clear(); if (Global.gfx_dlss_developer) { // the development library, with the indicator and the keys of the debug views auto const directory{program_directory()}; paths.push_back(((directory.empty() ? std::filesystem::path(".") : directory) / "ngx_dev").wstring()); } for (auto &path : paths) { path_list.push_back(path.data()); } common = NVSDK_NGX_FeatureCommonInfo{}; common.PathListInfo.Path = path_list.empty() ? nullptr : path_list.data(); common.PathListInfo.Length = static_cast(path_list.size()); } void fail(std::string const &Error) { if (error.empty()) { error = Error; } failed = true; } // a failed frame: the output black instead of what was in its memory, which the post-processing (the exposure too) reads void clear_output(VkCommandBuffer const Buffer) const { if (false == output.valid) { return; } VkClearColorValue const black{{0.f, 0.f, 0.f, 0.f}}; VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; vkCmdClearColorImage(Buffer, output.image, VK_IMAGE_LAYOUT_GENERAL, &black, 1, &range); VkMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; barrier.srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT; barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT; VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.memoryBarrierCount = 1; dependency.pMemoryBarriers = &barrier; vkCmdPipelineBarrier2(Buffer, &dependency); } static NVSDK_NGX_Resource_VK wrap(resource const &Resource, bool const Write) { VkImageSubresourceRange const range{Resource.aspect, 0, 1, 0, 1}; return NVSDK_NGX_Create_ImageView_Resource_VK(Resource.view, Resource.image, range, Resource.format, Resource.width, Resource.height, Write); } // the replay of the frame: the feature made when it isn't there, then evaluated static void record(VkCommandBuffer const Buffer, void *const Data) { auto &self{*static_cast(Data)}; if (self.failed || (self.parameters == nullptr)) { self.clear_output(Buffer); return; } if (self.handle == nullptr) { for (auto const *hint : {NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_DLAA, NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Quality, NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Balanced, NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_Performance, NVSDK_NGX_Parameter_DLSS_Hint_Render_Preset_UltraPerformance}) { NVSDK_NGX_Parameter_SetUI(self.parameters, hint, static_cast(self.wanted.preset)); } NVSDK_NGX_DLSS_Create_Params create{}; create.Feature.InWidth = static_cast(self.wanted.create.x); create.Feature.InHeight = static_cast(self.wanted.create.y); create.Feature.InTargetWidth = static_cast(self.wanted.output.x); create.Feature.InTargetHeight = static_cast(self.wanted.output.y); create.Feature.InPerfQualityValue = static_cast(self.wanted.mode); create.InFeatureCreateFlags = self.wanted.flags; create.InEnableOutputSubrects = false; auto const result{NGX_VULKAN_CREATE_DLSS_EXT1(self.device, Buffer, 1, 1, &self.handle, self.parameters, &create)}; if (NVSDK_NGX_FAILED(result) || (self.handle == nullptr)) { self.handle = nullptr; self.fail("the feature of dlss can't be made: " + result_text(result)); self.clear_output(Buffer); return; } self.made = self.wanted; } auto colour{wrap(self.colour, false)}; auto depth{wrap(self.depth, false)}; auto motion{wrap(self.motion, false)}; auto exposure{wrap(self.exposure, false)}; auto output{wrap(self.output, true)}; NVSDK_NGX_VK_DLSS_Eval_Params evaluation{}; evaluation.Feature.pInColor = &colour; evaluation.Feature.pInOutput = &output; evaluation.pInDepth = &depth; evaluation.pInMotionVectors = &motion; evaluation.pInExposureTexture = (self.exposure.valid ? &exposure : nullptr); evaluation.InJitterOffsetX = self.jitter.x; evaluation.InJitterOffsetY = self.jitter.y; evaluation.InRenderSubrectDimensions = {self.colour.width, self.colour.height}; evaluation.InReset = (self.reset ? 1 : 0); // the velocities are parts of the image, dlss takes pixels of the colour evaluation.InMVScaleX = static_cast(self.colour.width); evaluation.InMVScaleY = static_cast(self.colour.height); evaluation.InPreExposure = 1.f; evaluation.InExposureScale = 1.f; // the rows of the images go from the bottom of the picture up (decision K8): the indicator of the library the right way up evaluation.InIndicatorInvertYAxis = 1; evaluation.InFrameTimeDeltaInMsec = self.frame_time; auto const result{NGX_VULKAN_EVALUATE_DLSS_EXT(Buffer, self.handle, self.parameters, &evaluation)}; if (NVSDK_NGX_FAILED(result)) { self.fail("dlss can't be evaluated: " + result_text(result)); self.clear_output(Buffer); return; } // the memory of the features now and then, for the statistics (ngx isn't to be called from two threads at once) if ((self.evaluations++ % 120) == 0) { unsigned long long bytes{0}; if (NVSDK_NGX_SUCCEED(NGX_DLSS_GET_STATS(self.parameters, &bytes))) { self.memory = bytes; } } } // a failed frame generation: the frames it was to make are the backbuffer, shown again void copy_backbuffer(VkCommandBuffer const Buffer) const { if (false == backbuffer.valid) { return; } for (auto const &output : generated) { if (false == output.valid) { continue; } VkImageCopy2 region{}; region.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2; region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.extent = {std::min(backbuffer.width, output.width), std::min(backbuffer.height, output.height), 1}; VkCopyImageInfo2 copy{}; copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2; copy.srcImage = backbuffer.image; copy.srcImageLayout = VK_IMAGE_LAYOUT_GENERAL; copy.dstImage = output.image; copy.dstImageLayout = VK_IMAGE_LAYOUT_GENERAL; copy.regionCount = 1; copy.pRegions = ®ion; vkCmdCopyImage2(Buffer, ©); } VkMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT; VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.memoryBarrierCount = 1; dependency.pMemoryBarriers = &barrier; vkCmdPipelineBarrier2(Buffer, &dependency); } void fail_generation(std::string const &Error) { if (generation_error.empty()) { generation_error = Error; } generation_failed = true; } // the replay of the frame: the feature of the frame generation made when it isn't there, then evaluated once for each frame it // makes, from the first one shown on static void record_generation(VkCommandBuffer const Buffer, void *const Data) { auto &self{*static_cast(Data)}; if (self.generation_failed || (self.generation_parameters == nullptr) || self.generated.empty()) { self.copy_backbuffer(Buffer); return; } auto *parameters{self.generation_parameters}; if (self.generation_handle == nullptr) { NVSDK_NGX_DLSSG_Create_Params create{}; create.Width = static_cast(self.generation_wanted.output.x); create.Height = static_cast(self.generation_wanted.output.y); create.NativeBackbufferFormat = static_cast(self.generation_wanted.format); create.RenderWidth = static_cast(self.generation_wanted.render.x); create.RenderHeight = static_cast(self.generation_wanted.render.y); create.DynamicResolutionScaling = false; // the helper of vulkan leaves out the size of the depth and the velocities, which the one of d3d12 gives NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Width, create.Width); NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Height, create.Height); NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalWidth, create.RenderWidth); NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalHeight, create.RenderHeight); NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_DynamicResolution, 0); auto const result{NGX_VK_CREATE_DLSSG(Buffer, 1, 1, &self.generation_handle, parameters, &create)}; if (NVSDK_NGX_FAILED(result) || (self.generation_handle == nullptr)) { self.generation_handle = nullptr; self.fail_generation("the feature of the frame generation of dlss can't be made: " + result_text(result)); self.copy_backbuffer(Buffer); return; } self.generation_made = self.generation_wanted; } auto backbuffer{wrap(self.backbuffer, true)}; auto hudless{wrap(self.hudless, true)}; auto depth{wrap(self.generation_depth, true)}; auto motion{wrap(self.generation_motion, true)}; auto options{self.generation_options}; options.multiFrameCount = static_cast(self.generated.size()); for (std::size_t i = 0; i < self.generated.size(); ++i) { auto output{wrap(self.generated[i], true)}; NVSDK_NGX_VK_DLSSG_Eval_Params evaluation{}; evaluation.pBackbuffer = &backbuffer; evaluation.pDepth = &depth; evaluation.pMVecs = &motion; evaluation.pHudless = (self.hudless.valid ? &hudless : nullptr); evaluation.pOutputInterpFrame = &output; options.multiFrameIndex = static_cast(i + 1); auto const result{NGX_VK_EVALUATE_DLSSG(Buffer, self.generation_handle, parameters, &evaluation, &options)}; if (NVSDK_NGX_FAILED(result)) { self.fail_generation("the frame generation of dlss can't be evaluated: " + result_text(result)); self.copy_backbuffer(Buffer); return; } } ++self.generations; } void release_generation(deletion_queue *Deletions, std::uint64_t const Value) { if (generation_handle == nullptr) { return; } auto *old{generation_handle}; generation_handle = nullptr; generation_made = generation_feature{}; if (Deletions != nullptr) { Deletions->push(Value, [old]() { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); } else { NVSDK_NGX_VULKAN_ReleaseFeature(old); } } void release(deletion_queue *Deletions, std::uint64_t const Value) { if (handle == nullptr) { return; } auto *old{handle}; handle = nullptr; made = feature{}; if (Deletions != nullptr) { Deletions->push(Value, [old]() { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); } else { NVSDK_NGX_VULKAN_ReleaseFeature(old); } } }; dlss::dlss() : m_state{std::make_unique()} {} dlss::~dlss() { shutdown(); } bool dlss::compiled() { return true; } namespace { // the features of ngx the renderer uses: the super resolution, and the frame generation where it's asked for std::vector wanted_features() { std::vector features{NVSDK_NGX_Feature_SuperSampling}; if (Global.gfx_framegeneration > 1) { features.push_back(NVSDK_NGX_Feature_FrameGeneration); } return features; } void add_extensions(VkExtensionProperties const *Properties, std::uint32_t const Count, std::vector &Extensions) { for (std::uint32_t i = 0; i < Count; ++i) { std::string const name{Properties[i].extensionName}; if (std::find(Extensions.begin(), Extensions.end(), name) == Extensions.end()) { Extensions.push_back(name); } } } } // namespace void dlss::instance_extensions(std::vector &Extensions) { state temporary; temporary.prepare_paths(); for (auto const feature : wanted_features()) { auto const info{temporary.discovery(feature)}; std::uint32_t count{0}; VkExtensionProperties *properties{nullptr}; auto const result{NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(&info, &count, &properties)}; if (NVSDK_NGX_FAILED(result)) { log_warning(std::string("dlss: the instance extensions ") + (feature == NVSDK_NGX_Feature_FrameGeneration ? "its frame generation" : "it") + " needs can't be had from ngx: " + result_text(result)); continue; } add_extensions(properties, count, Extensions); } } void dlss::device_extensions(VkInstance const Instance, VkPhysicalDevice const Physical, std::vector &Extensions) { state temporary; temporary.prepare_paths(); for (auto const feature : wanted_features()) { auto const info{temporary.discovery(feature)}; std::uint32_t count{0}; VkExtensionProperties *properties{nullptr}; auto const result{NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(Instance, Physical, &info, &count, &properties)}; if (NVSDK_NGX_FAILED(result)) { log_warning(std::string("dlss: the device extensions ") + (feature == NVSDK_NGX_Feature_FrameGeneration ? "its frame generation" : "it") + " needs can't be had from ngx: " + result_text(result)); continue; } add_extensions(properties, count, Extensions); } } bool dlss::init(VkInstance const Instance, VkPhysicalDevice const Physical, VkDevice const Device, std::string &Reason) { auto &s{*m_state}; if (s.initialized) { return s.available; } s.prepare_paths(); s.instance = Instance; s.physical = Physical; s.device = Device; auto const result{NVSDK_NGX_VULKAN_Init_with_ProjectID(project_id, NVSDK_NGX_ENGINE_TYPE_CUSTOM, engine_version, s.data_path.c_str(), Instance, Physical, Device, vkGetInstanceProcAddr, vkGetDeviceProcAddr, &s.common, NVSDK_NGX_Version_API)}; if (NVSDK_NGX_FAILED(result)) { Reason = "ngx can't start: " + result_text(result); return false; } s.initialized = true; // the parameters with the capabilities in them (the drivers before 450 don't give them this way, and are too old anyway) auto const capabilities{NVSDK_NGX_VULKAN_GetCapabilityParameters(&s.parameters)}; if (NVSDK_NGX_FAILED(capabilities) || (s.parameters == nullptr)) { s.parameters = nullptr; Reason = "ngx gives no parameters, the graphics driver is probably too old: " + result_text(capabilities); shutdown(); return false; } int updatedriver{0}; if (NVSDK_NGX_SUCCEED(s.parameters->Get(NVSDK_NGX_Parameter_SuperSampling_NeedsUpdatedDriver, &updatedriver)) && (updatedriver != 0)) { unsigned int major{0}, minor{0}; s.parameters->Get(NVSDK_NGX_Parameter_SuperSampling_MinDriverVersionMajor, &major); s.parameters->Get(NVSDK_NGX_Parameter_SuperSampling_MinDriverVersionMinor, &minor); Reason = "the graphics driver is too old for dlss, it needs " + std::to_string(major) + "." + std::to_string(minor) + " or newer"; shutdown(); return false; } int supported{0}; if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_Parameter_SuperSampling_Available, &supported)) || (supported == 0)) { Reason = "dlss isn't available on this gpu (it needs a geforce rtx) or with this driver"; shutdown(); return false; } supported = 0; if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_Parameter_SuperSampling_FeatureInitResult, &supported)) || (supported == 0)) { Reason = "dlss is denied to the program by the driver"; shutdown(); return false; } s.available = true; // the frame generation, where it's asked for; without it dlss goes on alone if (Global.gfx_framegeneration > 1) { std::string reason; int fg{0}; int fgupdate{0}; unsigned int framesmax{0}; if (NVSDK_NGX_SUCCEED(s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_NeedsUpdatedDriver, &fgupdate)) && (fgupdate != 0)) { unsigned int major{0}, minor{0}; s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMajor, &major); s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMinor, &minor); reason = "the graphics driver is too old for it, it needs " + std::to_string(major) + "." + std::to_string(minor) + " or newer"; } else if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_Available, &fg)) || (fg == 0)) { reason = "it isn't available on this gpu (it needs a geforce rtx of the 40 series or newer) or with this driver"; } else if (NVSDK_NGX_FAILED(NVSDK_NGX_VULKAN_AllocateParameters(&s.generation_parameters)) || (s.generation_parameters == nullptr)) { s.generation_parameters = nullptr; reason = "ngx gives no parameters for it"; } else { // more than one frame between two (x3, x4) from the rtx 50 series on if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_DLSSG_Parameter_MultiFrameCountMax, &framesmax)) || (framesmax < 1)) { framesmax = 1; } s.generation_max = std::min(framesmax, 3u); } if (s.generation_max == 0) { log_info("gfx.framegeneration: " + reason + ", the frames aren't generated"); } else { log_info("dlss: the frame generation can make up to " + std::to_string(s.generation_max) + (s.generation_max == 1 ? " frame" : " frames") + " between two (x" + std::to_string(s.generation_max + 1) + ")"); } } return true; } void dlss::shutdown() { auto &s{*m_state}; if (false == s.initialized) { return; } // the gpu is idle: the features go at once s.release(nullptr, 0); s.release_generation(nullptr, 0); if (s.generation_parameters != nullptr) { NVSDK_NGX_VULKAN_DestroyParameters(s.generation_parameters); } s.generation_parameters = nullptr; s.generation_max = 0; if (s.parameters != nullptr) { NVSDK_NGX_VULKAN_DestroyParameters(s.parameters); } s.parameters = nullptr; NVSDK_NGX_VULKAN_Shutdown1(s.device); s.initialized = false; s.available = false; } bool dlss::available() const { return m_state->available; } bool dlss::plan(glm::ivec2 const &Output, float const Scale, glm::ivec2 &Render) { auto &s{*m_state}; // the smallest window dlss upscales to if ((false == s.available) || (Output.x < 64) || (Output.y < 32)) { return false; } if (Scale >= 0.999f) { s.plan_render = Output; s.plan_create = Output; s.plan_output = Output; s.plan_mode = NVSDK_NGX_PerfQuality_Value_DLAA; Render = Output; log_info("dlss: dlaa, the scene drawn at the size of the window, " + std::to_string(Output.x) + "x" + std::to_string(Output.y)); return true; } glm::ivec2 const wanted{glm::max(glm::ivec2(glm::round(glm::vec2(Output) * Scale)), glm::ivec2(1))}; auto bestmode{-1}; glm::ivec2 best{0}; glm::ivec2 bestoptimal{0}; auto bestinside{false}; auto bestdistance{1e9f}; for (auto const mode : upscaling_modes) { unsigned int optimalwidth{0}, optimalheight{0}, maxwidth{0}, maxheight{0}, minwidth{0}, minheight{0}; float sharpness{0.f}; auto const result{NGX_DLSS_GET_OPTIMAL_SETTINGS(s.parameters, static_cast(Output.x), static_cast(Output.y), mode, &optimalwidth, &optimalheight, &maxwidth, &maxheight, &minwidth, &minheight, &sharpness)}; if (NVSDK_NGX_FAILED(result) || (optimalwidth == 0) || (optimalheight == 0)) { // a mode not there for this size continue; } glm::ivec2 const low{static_cast(minwidth), static_cast(minheight)}; glm::ivec2 const high{static_cast(std::max(maxwidth, minwidth)), static_cast(std::max(maxheight, minheight))}; auto const inside{(wanted.x >= low.x) && (wanted.y >= low.y) && (wanted.x <= high.x) && (wanted.y <= high.y)}; auto const distance{std::abs(static_cast(optimalwidth) / static_cast(Output.x) - Scale)}; // a mode the size fits in goes before one it has to be brought into, then the one whose own size is nearest if ((bestmode < 0) || (inside && (false == bestinside)) || ((inside == bestinside) && (distance < bestdistance))) { bestmode = mode; // brought into the range of the mode by one scale for both sides, which keeps the shape of the window auto const lowscale{std::max(static_cast(low.x) / static_cast(Output.x), static_cast(low.y) / static_cast(Output.y))}; auto const highscale{std::min(static_cast(high.x) / static_cast(Output.x), static_cast(high.y) / static_cast(Output.y))}; auto const scale{std::clamp(Scale, std::min(lowscale, highscale), highscale)}; best = inside ? wanted : glm::clamp(glm::ivec2(glm::round(glm::vec2(Output) * scale)), low, high); bestoptimal = {static_cast(optimalwidth), static_cast(optimalheight)}; bestinside = inside; bestdistance = distance; } } if (bestmode < 0) { log_warning("dlss: no mode of it upscales to " + std::to_string(Output.x) + "x" + std::to_string(Output.y)); return false; } s.plan_render = best; // the feature made for the size the guide gives for the mode (3.2.2), or for the size drawn when that's larger: the presets l and // m make their buffers for the size given at the creation, and make them anew for a larger image (3.2.2.1) s.plan_create = glm::max(bestoptimal, best); s.plan_output = Output; s.plan_mode = bestmode; Render = best; log_info("dlss: the scene drawn at " + std::to_string(best.x) + "x" + std::to_string(best.y) + " for the window of " + std::to_string(Output.x) + "x" + std::to_string(Output.y) + ", mode " + mode_name(bestmode) + (bestinside ? std::string{} : ", gfx.buffer.percent brought into the range of the mode")); return true; } float dlss::lod_bias() const { auto const &s{*m_state}; if ((s.plan_output.x <= 0) || (s.plan_render.x <= 0)) { return 0.f; } // the textures sampled as at the size of the window (the dlss programming guide, 3.5) return std::log2(static_cast(s.plan_render.x) / static_cast(s.plan_output.x)) - 1.f + Global.gfx_dlss_lodbias; } std::uint32_t dlss::jitter_phases() const { auto const &s{*m_state}; if ((s.plan_output.x <= 0) || (s.plan_render.x <= 0)) { return 8; } // 8 offsets for each pixel of the window (the dlss programming guide, 3.7.1.1) auto const ratio{static_cast(s.plan_output.x) / static_cast(s.plan_render.x)}; return static_cast(std::clamp(std::ceil(8.f * ratio * ratio), 8.f, 128.f)); } void dlss::evaluate(command_stream &Commands, frame_inputs const &Inputs, deletion_queue &Deletions, std::uint64_t const Value) { auto &s{*m_state}; if ((false == s.available) || s.failed || (Inputs.colour == nullptr) || (Inputs.depth == nullptr) || (Inputs.motion == nullptr) || (Inputs.output == nullptr)) { return; } state::feature feature; feature.render = {static_cast(Inputs.colour->desc().extent.width), static_cast(Inputs.colour->desc().extent.height)}; feature.output = {static_cast(Inputs.output->desc().extent.width), static_cast(Inputs.output->desc().extent.height)}; auto const planned{(feature.render == s.plan_render) && (feature.output == s.plan_output)}; feature.mode = (feature.render == feature.output) ? static_cast(NVSDK_NGX_PerfQuality_Value_DLAA) : planned ? s.plan_mode : static_cast(NVSDK_NGX_PerfQuality_Value_MaxQuality); feature.create = planned ? s.plan_create : feature.render; // the colour is linear (hdr), the velocities have the size of the colour, the depth is reverse z feature.flags = NVSDK_NGX_DLSS_Feature_Flags_IsHDR | NVSDK_NGX_DLSS_Feature_Flags_MVLowRes | NVSDK_NGX_DLSS_Feature_Flags_DepthInverted | (Inputs.exposure == nullptr ? NVSDK_NGX_DLSS_Feature_Flags_AutoExposure : 0); feature.preset = Global.gfx_dlss_preset; if ((s.handle != nullptr) && (false == (feature == s.made))) { s.release(&Deletions, Value); } s.wanted = feature; auto const describe = [](image const *Image, state::resource &Resource) { Resource.valid = (Image != nullptr); if (Image == nullptr) { return; } Resource.image = Image->handle(); Resource.view = Image->view(); Resource.format = Image->desc().format; Resource.aspect = Image->aspect(); Resource.width = Image->desc().extent.width; Resource.height = Image->desc().extent.height; }; describe(Inputs.colour, s.colour); describe(Inputs.depth, s.depth); describe(Inputs.motion, s.motion); describe(Inputs.exposure, s.exposure); describe(Inputs.output, s.output); s.jitter = Inputs.jitter * glm::vec2{s.flip_x ? -1.f : 1.f, s.flip_y ? -1.f : 1.f}; s.reset = Inputs.reset || s.reset_wanted; s.reset_wanted = false; s.frame_time = Inputs.frame_time; Commands.external(&state::record, &s); } std::uint32_t dlss::generation_max() const { auto const &s{*m_state}; return (s.available ? s.generation_max : 0); } bool dlss::generation_failed() const { return m_state->seen_generation_failed; } namespace { // a matrix of glm (columns of column vectors) as ngx takes it: rows, for row vectors multiplied by it from the left, which is the // same memory void ngx_matrix(glm::mat4 const &Matrix, float (&Target)[4][4]) { std::memcpy(Target, &Matrix[0][0], sizeof(Target)); } } // namespace void dlss::generate(command_stream &Commands, generation_inputs const &Inputs, deletion_queue &Deletions, std::uint64_t const Value) { auto &s{*m_state}; if ((false == s.available) || (s.generation_max == 0) || (Inputs.backbuffer == nullptr) || (Inputs.depth == nullptr) || (Inputs.motion == nullptr) || Inputs.outputs.empty()) { return; } state::generation_feature feature; feature.output = {static_cast(Inputs.backbuffer->desc().extent.width), static_cast(Inputs.backbuffer->desc().extent.height)}; feature.render = {static_cast(Inputs.depth->desc().extent.width), static_cast(Inputs.depth->desc().extent.height)}; feature.format = Inputs.backbuffer->desc().format; if ((s.generation_handle != nullptr) && (false == (feature == s.generation_made))) { s.release_generation(&Deletions, Value); } s.generation_wanted = feature; auto const describe = [](image const *Image, state::resource &Resource) { Resource.valid = (Image != nullptr); if (Image == nullptr) { return; } Resource.image = Image->handle(); Resource.view = Image->view(); Resource.format = Image->desc().format; Resource.aspect = Image->aspect(); Resource.width = Image->desc().extent.width; Resource.height = Image->desc().extent.height; }; describe(Inputs.backbuffer, s.backbuffer); describe(Inputs.hudless, s.hudless); describe(Inputs.depth, s.generation_depth); describe(Inputs.motion, s.generation_motion); s.generated.resize(std::min(Inputs.outputs.size(), s.generation_max)); for (std::size_t i = 0; i < s.generated.size(); ++i) { describe(Inputs.outputs[i], s.generated[i]); } auto &options{s.generation_options}; options = NVSDK_NGX_DLSSG_Opt_Eval_Params{}; // the clip space of the renderer has its rows from the bottom of the picture up, as the images; the images the frame generation // is given are turned over, and its clip space (that of d3d12, y up the picture) is the one of the renderer as it is. the debug // window turns it over for the other way glm::mat4 flip{1.f}; if (s.generation_clip_flip) { flip[1][1] = -1.f; } auto const viewtoclip{flip * Inputs.view_to_clip}; auto const cliptoprevious{flip * Inputs.clip_to_previous_clip * flip}; ngx_matrix(viewtoclip, options.cameraViewToClip); ngx_matrix(glm::inverse(viewtoclip), options.clipToCameraView); ngx_matrix(glm::mat4{1.f}, options.clipToLensClip); ngx_matrix(cliptoprevious, options.clipToPrevClip); ngx_matrix(glm::inverse(cliptoprevious), options.prevClipToClip); // the offset as dlss takes it, along the rows turned over auto const jitter{Inputs.jitter * glm::vec2{s.flip_x ? -1.f : 1.f, s.flip_y ? 1.f : -1.f}}; options.jitterOffset[0] = jitter.x; options.jitterOffset[1] = jitter.y; // the velocities are parts of the image along the rows of the renderer: pixels of the image turned over options.mvecScale[0] = static_cast(s.generation_depth.width); options.mvecScale[1] = -static_cast(s.generation_depth.height); options.cameraPinholeOffset[0] = 0.f; options.cameraPinholeOffset[1] = 0.f; for (int i = 0; i < 3; ++i) { options.cameraPos[i] = Inputs.position[i]; options.cameraUp[i] = Inputs.up[i]; options.cameraRight[i] = Inputs.right[i]; options.cameraFwd[i] = Inputs.forward[i]; } options.cameraNear = Inputs.znear; options.cameraFar = Inputs.zfar; options.cameraFOV = Inputs.fov; options.cameraAspectRatio = Inputs.aspect; options.colorBuffersHDR = false; options.depthInverted = true; options.cameraMotionIncluded = true; options.reset = Inputs.reset || s.generation_reset_wanted; s.generation_reset_wanted = false; options.automodeOverrideReset = false; options.notRenderingGameFrames = false; options.orthoProjection = false; // no value the velocities have marks them as missing options.motionVectorsInvalidValue = FLT_MAX; options.motionVectorsDilated = false; options.menuDetectionEnabled = false; options.mvecsSubrectSize = {s.generation_motion.width, s.generation_motion.height}; options.depthSubrectSize = {s.generation_depth.width, s.generation_depth.height}; options.hudLessSubrectSize = {s.hudless.width, s.hudless.height}; options.backbufferSubrectSize = {s.backbuffer.width, s.backbuffer.height}; options.outputInterpSubrectSize = {s.backbuffer.width, s.backbuffer.height}; Commands.external(&state::record_generation, &s); } std::string dlss::sync() { auto &s{*m_state}; s.seen_failed = s.failed; s.seen_error = s.error; s.seen_feature = ((s.handle != nullptr) ? s.made : s.wanted); s.seen_memory = s.memory; s.seen_generation_failed = s.generation_failed; s.seen_generations = s.generations; if (s.failed && (false == s.error_told)) { s.error_told = true; return s.error; } if (s.generation_failed && (false == s.generation_error_told)) { s.generation_error_told = true; return s.generation_error; } return {}; } bool dlss::failed() const { return m_state->seen_failed; } std::string dlss::info() const { auto const &s{*m_state}; if (false == s.available) { return "dlss: not available"; } if (s.seen_failed) { return "dlss: off after an error (" + s.seen_error + ")"; } auto const &feature{s.seen_feature}; if (feature.mode < 0) { return "dlss: ready, not used yet"; } // the presets l and m take no exposure, they work it out themselves (the dlss programming guide, 3.9) return std::string("dlss: ") + mode_name(feature.mode) + ", preset " + preset_name(feature.preset) + ", " + std::to_string(feature.render.x) + "x" + std::to_string(feature.render.y) + " to " + std::to_string(feature.output.x) + "x" + std::to_string(feature.output.y) + ((feature.flags & NVSDK_NGX_DLSS_Feature_Flags_AutoExposure) != 0 ? ", its own exposure" : ", given the exposure of the tonemapping") + ", " + std::to_string(s.seen_memory / (1024 * 1024)) + " MiB" + (s.generation_max == 0 ? std::string{} : s.seen_generation_failed ? ", the frame generation off after an error" : ", the frame generation up to x" + std::to_string(s.generation_max + 1) + " (" + std::to_string(s.seen_generations) + " frames generated from)"); } void dlss::draw_debug_ui() { auto &s{*m_state}; ImGui::TextUnformatted(info().c_str()); if (false == s.available) { return; } ImGui::Checkbox("dlss: jitter offset turned the other way along the rows", &s.flip_x); ImGui::SameLine(); ImGui::Checkbox("along the columns", &s.flip_y); if (ImGui::Button("dlss: drop its history")) { s.reset_wanted = true; } if (s.generation_max > 0) { ImGui::Checkbox("frame generation: the clip space of its matrices turned over", &s.generation_clip_flip); ImGui::SameLine(); if (ImGui::Button("drop its history")) { s.generation_reset_wanted = true; } } } #else // without the sdk: dlss is never available struct dlss::state { }; dlss::dlss() : m_state{std::make_unique()} {} dlss::~dlss() = default; bool dlss::compiled() { return false; } void dlss::instance_extensions(std::vector &) {} void dlss::device_extensions(VkInstance, VkPhysicalDevice, std::vector &) {} bool dlss::init(VkInstance, VkPhysicalDevice, VkDevice, std::string &Reason) { Reason = "the program was built without the dlss sdk (WITH_DLSS)"; return false; } void dlss::shutdown() {} bool dlss::available() const { return false; } bool dlss::plan(glm::ivec2 const &, float, glm::ivec2 &) { return false; } float dlss::lod_bias() const { return 0.f; } std::uint32_t dlss::jitter_phases() const { return 8; } void dlss::evaluate(command_stream &, frame_inputs const &, deletion_queue &, std::uint64_t) {} std::uint32_t dlss::generation_max() const { return 0; } bool dlss::generation_failed() const { return false; } void dlss::generate(command_stream &, generation_inputs const &, deletion_queue &, std::uint64_t) {} std::string dlss::sync() { return {}; } bool dlss::failed() const { return false; } std::string dlss::info() const { return "dlss: not in this build (WITH_DLSS)"; } void dlss::draw_debug_ui() { ImGui::TextUnformatted(info().c_str()); } #endif } // namespace vk14