17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-09 00:17:39 +01:00

Add DLSS Frame Generation (gfx.framegeneration x2/x3/x4) and Reflex (gfx.reflex) to the Vulkan renderer

This commit is contained in:
2026-10-25 20:52:12 +01:00
parent 1841e6d7b0
commit a6bdeeaa2b
23 changed files with 1474 additions and 85 deletions

View File

@@ -19,10 +19,13 @@ http://mozilla.org/MPL/2.0/.
#if defined(WITH_DLSS)
#include <nvsdk_ngx_helpers.h>
#include <nvsdk_ngx_helpers_vk.h>
#include <nvsdk_ngx_helpers_dlssg_vk.h>
#endif
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <system_error>
@@ -196,16 +199,48 @@ struct dlss::state
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<resource> 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() const
NVSDK_NGX_FeatureDiscoveryInfo discovery(NVSDK_NGX_Feature const Feature) const
{
NVSDK_NGX_FeatureDiscoveryInfo info{};
info.SDKVersion = NVSDK_NGX_Version_API;
info.FeatureID = NVSDK_NGX_Feature_SuperSampling;
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;
@@ -354,6 +389,138 @@ struct dlss::state
}
}
// 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 = &region;
vkCmdCopyImage2(Buffer, &copy);
}
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<state *>(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<unsigned int>(self.generation_wanted.output.x);
create.Height = static_cast<unsigned int>(self.generation_wanted.output.y);
create.NativeBackbufferFormat = static_cast<unsigned int>(self.generation_wanted.format);
create.RenderWidth = static_cast<unsigned int>(self.generation_wanted.render.x);
create.RenderHeight = static_cast<unsigned int>(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<unsigned int>(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<unsigned int>(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)
@@ -386,22 +553,50 @@ 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<NVSDK_NGX_Feature> wanted_features()
{
std::vector<NVSDK_NGX_Feature> 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<std::string> &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<std::string> &Extensions)
{
state temporary;
temporary.prepare_paths();
auto const info{temporary.discovery()};
std::uint32_t count{0};
VkExtensionProperties *properties{nullptr};
auto const result{NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(&info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
for (auto const feature : wanted_features())
{
log_warning("dlss: the instance extensions it needs can't be had from ngx: " + result_text(result));
return;
}
for (std::uint32_t i = 0; i < count; ++i)
{
Extensions.emplace_back(properties[i].extensionName);
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);
}
}
@@ -409,18 +604,18 @@ void dlss::device_extensions(VkInstance const Instance, VkPhysicalDevice const P
{
state temporary;
temporary.prepare_paths();
auto const info{temporary.discovery()};
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))
for (auto const feature : wanted_features())
{
log_warning("dlss: the device extensions it needs can't be had from ngx: " + result_text(result));
return;
}
for (std::uint32_t i = 0; i < count; ++i)
{
Extensions.emplace_back(properties[i].extensionName);
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);
}
}
@@ -477,6 +672,47 @@ bool dlss::init(VkInstance const Instance, VkPhysicalDevice const Physical, VkDe
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;
}
@@ -487,8 +723,15 @@ void dlss::shutdown()
{
return;
}
// the gpu is idle: the feature goes at once
// 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);
@@ -643,6 +886,125 @@ void dlss::evaluate(command_stream &Commands, frame_inputs const &Inputs, deleti
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<int>(Inputs.backbuffer->desc().extent.width), static_cast<int>(Inputs.backbuffer->desc().extent.height)};
feature.render = {static_cast<int>(Inputs.depth->desc().extent.width), static_cast<int>(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<std::size_t>(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<float>(s.generation_depth.width);
options.mvecScale[1] = -static_cast<float>(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};
@@ -650,12 +1012,19 @@ std::string dlss::sync()
s.seen_error = s.error;
s.seen_feature = ((s.handle != nullptr) ? s.made : s.wanted);
s.seen_memory = s.memory;
if ((false == s.failed) || s.error_told)
s.seen_generation_failed = s.generation_failed;
s.seen_generations = s.generations;
if (s.failed && (false == s.error_told))
{
return {};
s.error_told = true;
return s.error;
}
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
@@ -682,7 +1051,10 @@ std::string dlss::info() const
// 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";
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()
@@ -700,6 +1072,15 @@ void dlss::draw_debug_ui()
{
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
@@ -751,6 +1132,18 @@ std::uint32_t dlss::jitter_phases() const
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 {};