17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 02:18:35 +01:00

Add NVIDIA DLSS to the Vulkan renderer (gfx.antialiasing dlss/dlaa) and gfx.buffer.percent

This commit is contained in:
2026-10-25 19:35:33 +01:00
parent 71a6628a4a
commit 1841e6d7b0
27 changed files with 1487 additions and 56 deletions

View File

@@ -15,8 +15,6 @@ http://mozilla.org/MPL/2.0/.
namespace
{
// the offsets go round this many frames: enough to cover a pixel evenly, few enough for the history to hold all of them
constexpr std::uint32_t JITTER_PHASES{8};
// the camera moved farther than this from one frame to the next (another camera, another vehicle, a jump): the history is dropped
constexpr double CAMERA_JUMP{50.0};
@@ -38,7 +36,8 @@ float halton(std::uint32_t Index, std::uint32_t const Base)
glm::mat4 gfx::temporal_aa::begin_frame(glm::mat4 const &Projection, glm::dmat3 const &View, glm::dvec3 const &Camera, TDynamicObject const *Cab, glm::ivec2 const &Size)
{
m_phase = (m_phase + 1) % JITTER_PHASES;
// the offsets go round m_phases frames: enough to cover a pixel evenly, few enough for the history to hold all of them
m_phase = (m_phase + 1) % m_phases;
// the first element of the sequence is 0 in every base; from the second one on they are spread over the pixel
m_jitter = glm::vec2{halton(m_phase + 1, 2), halton(m_phase + 1, 3)} - 0.5f;
glm::dvec2 const size{static_cast<double>(std::max(Size.x, 1)), static_cast<double>(std::max(Size.y, 1))};

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <algorithm>
#include <cstdint>
#include <glm/glm.hpp>
@@ -39,6 +40,12 @@ class temporal_aa
{
m_previous.valid = false;
}
// the offsets go round Phases frames (at least 1); 8 for the temporal anti-aliasing, more when dlss upscales (8 for each pixel
// of the window)
void set_phases(std::uint32_t const Phases)
{
m_phases = std::max<std::uint32_t>(Phases, 1);
}
// the frame has a history: the last frame was drawn the same way
bool history_valid() const
{
@@ -83,6 +90,7 @@ class temporal_aa
frame m_previous;
std::uint32_t m_phase{0};
std::uint32_t m_phases{8};
glm::vec2 m_jitter{0.f};
glm::mat4 m_worldfuture{1.f};
glm::mat4 m_cabfuture{1.f};

View File

@@ -74,6 +74,13 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
if (!Init_caps())
return false;
if (Global.gfx_antialiasing == global_settings::antialiasing_mode::dlss)
{
// dlss is of the vulkan renderer only: the temporal anti-aliasing of this one instead
WriteLog("gfx.antialiasing dlss isn't available with opengl, using taa");
Global.gfx_antialiasing = global_settings::antialiasing_mode::taa;
}
WriteLog("preparing renderer..");
m_window = Window;
@@ -6744,12 +6751,15 @@ bool opengl46_renderer::Init_caps()
WriteLog("using multisampling x" + std::to_string(1 << Global.iMultisampling));
}
if (Global.gfx_framebuffer_width == -1)
Global.gfx_framebuffer_width = Global.fb_size.x;
if (Global.gfx_framebuffer_height == -1)
Global.gfx_framebuffer_height = Global.fb_size.y;
// the image of the main window: a part of the window (gfx.buffer.percent), stretched to it
if ((Global.gfx_framebuffer_width == -1) || (Global.gfx_framebuffer_height == -1))
{
auto const size{Global.gfx_buffer_size()};
Global.gfx_framebuffer_width = size.x;
Global.gfx_framebuffer_height = size.y;
}
WriteLog("main window size: " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height));
WriteLog("main window size: " + std::to_string(Global.fb_size.x) + "x" + std::to_string(Global.fb_size.y) + ", drawn at " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height));
return true;
}

View File

@@ -64,7 +64,8 @@ enum call_type : std::uint32_t
call_end_query,
call_begin_label,
call_end_label,
call_build_acceleration_structures
call_build_acceleration_structures,
call_external
};
struct call_header
@@ -174,6 +175,11 @@ struct build_call
std::size_t geometries;
std::size_t ranges;
};
struct external_call
{
command_stream::external_function function;
void *data;
};
// the copies and the blit: the info with its regions
template <typename Info_> struct copy_call
{
@@ -728,6 +734,14 @@ void command_stream::build_acceleration_structures(std::uint32_t const Count, Vk
work();
}
void command_stream::external(external_function const Function, void *const Data)
{
assert(m_rendering == no_call);
auto const call{add(call_external, sizeof(external_call))};
payload<external_call>(call) = external_call{Function, Data};
work();
}
// the replay
void command_stream::execute(std::size_t const Call, VkCommandBuffer const Buffer, VkRenderingFlags const Flags) const
@@ -1015,6 +1029,12 @@ void command_stream::execute(std::size_t const Call, VkCommandBuffer const Buffe
vkCmdBuildAccelerationStructuresKHR(Buffer, data.count, infos.data(), ranges.data());
break;
}
case call_external:
{
auto const &data{payload<external_call>(Call)};
data.function(Buffer, data.data);
break;
}
default:
break;
}

View File

@@ -80,6 +80,11 @@ class command_stream
void end_debug_utils_label();
// the builds of acceleration structures (VK_KHR_acceleration_structure, gfx.raytracing), outside of a rendering
void build_acceleration_structures(std::uint32_t Count, VkAccelerationStructureBuildGeometryInfoKHR const *Infos, VkAccelerationStructureBuildRangeInfoKHR const *const *Ranges);
// a call of a library recording its own commands (the evaluation of dlss), outside of a rendering: Function gets the command
// buffer of the replay and Data, which has to live until the replay is done. what it binds is left as it leaves it, so the
// calls after it bind what they use again
using external_function = void (*)(VkCommandBuffer Buffer, void *Data);
void external(external_function Function, void *Data);
// the stream is made anew
void clear();

View File

@@ -33,6 +33,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14particles.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14raytracing.h"
#include "rendering/vulkan14/vulkan14dlss.h"
#include "utilities/workerpool.h"
#include "model/material.h"
#include "gl/ubo.h"
@@ -119,6 +120,14 @@ struct viewport_targets
std::shared_ptr<image> colour;
std::shared_ptr<image> motion_image;
std::shared_ptr<image> depth;
// the size of the images after the upscaling of dlss (the window), the same as extent without it; dlss is on for the viewport
VkExtent2D output_extent{0, 0};
bool dlss{false};
// dlss (gfx.antialiasing dlss): the velocities it takes (those of the sky made from the motion of the camera), the exposure of the
// tonemapping in a texel, and its output, the size of the window (hdr, a storage image)
std::shared_ptr<image> dlss_motion;
std::shared_ptr<image> dlss_exposure;
std::shared_ptr<image> dlss_output;
// the colour after the motion blur (hdr)
std::shared_ptr<image> blurred;
// the temporal anti-aliasing (gfx.antialiasing taa, the main viewport): the two images of the history, written in turn (hdr, the
@@ -459,6 +468,9 @@ struct vulkan14_renderer::context
vk14::resources resources;
vk14::imgui_backend imgui;
vk14::scene_state scene;
// dlss of the main viewport (gfx.antialiasing dlss), with the bias of the mipmaps of the textures of the scene it wants
vk14::dlss dlss;
float lod_bias{0.f};
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports

View File

@@ -301,6 +301,12 @@ void vulkan14_renderer::draw_debug_ui()
ImGui::Separator();
gfx::exposure_settings::draw_debug_ui(scene.exposure.values);
ImGui::Separator();
// dlss (gfx.antialiasing dlss), set at the start
if (Global.gfx_dlss() || (m_dlssrender.x > 0))
{
m_context->dlss.draw_debug_ui();
ImGui::Separator();
}
// the ambient occlusion (opengl46_ao::draw_debug_ui()): its images are made with the targets of the viewports
if (scene.targets.empty() || (scene.targets.front().ssao == nullptr))
{

View File

@@ -17,10 +17,12 @@ namespace vk14
// samplers
bool samplers::create(device const &Device, float Anisotropy, std::string &Reason)
bool samplers::create(device const &Device, float Anisotropy, std::string &Reason, float const LodBias)
{
auto const handle{Device.handle()};
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
auto const maxbias{Device.caps().limits.maxSamplerLodBias};
auto const lodbias{std::clamp(LodBias, -maxbias, maxbias)};
VkFormatProperties depthformat{};
vkGetPhysicalDeviceFormatProperties(Device.physical(), VK_FORMAT_D32_SFLOAT, &depthformat);
auto const depthlinear{(depthformat.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0};
@@ -39,6 +41,12 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
info.anisotropyEnable = VK_TRUE;
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
}
// the textures of the scene (the mipmapped samplers with the anisotropy of the settings), sampled as at the size of the
// window when the scene is drawn smaller for dlss
if ((&Sampler == &repeat_aniso) || (&Sampler == &clamp_aniso))
{
info.mipLodBias = lodbias;
}
// the border of the clamped samplers: the farthest depth of the reverse z, black
info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
if (Compare)

View File

@@ -35,7 +35,8 @@ struct samplers
// the device filters the depth format, the border the farthest depth (0)
VkSampler depth{VK_NULL_HANDLE};
bool create(device const &Device, float Anisotropy, std::string &Reason);
// LodBias: the bias of the mipmaps of the textures of the scene (the anisotropic ones), for dlss
bool create(device const &Device, float Anisotropy, std::string &Reason, float LodBias = 0.f);
void destroy(VkDevice Device);
};

View File

@@ -234,7 +234,7 @@ bool device::select(VkInstance Instance, VkSurfaceKHR Surface, std::string const
return true;
}
bool device::create(instance const &Instance, std::string &Reason)
bool device::create(instance const &Instance, std::string &Reason, std::vector<std::string> const &Wanted)
{
if (m_device != VK_NULL_HANDLE)
{
@@ -271,6 +271,9 @@ bool device::create(instance const &Instance, std::string &Reason)
// everything else the renderer uses is in the core of vulkan 1.4
std::vector<char const *> extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
// the names asked for by a library, kept for the call which makes the device
std::vector<std::string> wantednames;
wantednames.reserve(Wanted.size());
{
// a device of the portability subset (vulkan on metal) has to be told the program knows it
std::uint32_t count{0};
@@ -288,6 +291,38 @@ bool device::create(instance const &Instance, std::string &Reason)
{
extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
// what a library asks for (dlss, with the features of vulkan 1.2 it takes for granted). the buffer device address of ext
// can't go with the one of the core, it's taken as the extension of khr
for (auto name : Wanted)
{
if (name == VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME)
{
name = VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME;
}
if (std::any_of(extensions.begin(), extensions.end(), [&](char const *Entry) { return name == Entry; }))
{
continue;
}
if (std::none_of(available.begin(), available.end(), [&](VkExtensionProperties const &Entry) { return name == Entry.extensionName; }))
{
log_warning("the device extension " + name + " asked for isn't available");
continue;
}
if (std::find(wantednames.begin(), wantednames.end(), name) == wantednames.end())
{
wantednames.push_back(name);
}
}
for (auto const &name : wantednames)
{
extensions.push_back(name.c_str());
}
if (false == Wanted.empty())
{
enabled.v12.timelineSemaphore = m_queried.v12.timelineSemaphore;
enabled.v12.descriptorIndexing = m_queried.v12.descriptorIndexing;
enabled.v12.bufferDeviceAddress = m_queried.v12.bufferDeviceAddress;
}
}
// the ray traced reflections, asked for in eu07.ini (gfx.raytracing), where the device has what they need

View File

@@ -48,7 +48,8 @@ class device
// glfw). Verbose: every gpu and why it can't be used goes to the log. false: no usable gpu, Reason tells why
bool select(VkInstance Instance, VkSurfaceKHR Surface, std::string const &Wanted, bool const Verbose, std::string &Reason);
// makes the VkDevice of the gpu picked by select()
bool create(instance const &Instance, std::string &Reason);
// Wanted: the extensions a library asks for (dlss), taken where the device has them, with the features of vulkan 1.2 it uses
bool create(instance const &Instance, std::string &Reason, std::vector<std::string> const &Wanted = {});
void destroy();
VkPhysicalDevice physical() const

View File

@@ -0,0 +1,776 @@
/*
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 <nvsdk_ngx_helpers.h>
#include <nvsdk_ngx_helpers_vk.h>
#endif
#include <algorithm>
#include <cmath>
#include <filesystem>
#include <system_error>
#if defined(WITH_DLSS) && !defined(_WIN32)
#include <unistd.h>
#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<unsigned int>(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<char>(*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<std::wstring> paths;
std::vector<wchar_t *> 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 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};
NVSDK_NGX_FeatureDiscoveryInfo discovery() const
{
NVSDK_NGX_FeatureDiscoveryInfo info{};
info.SDKVersion = NVSDK_NGX_Version_API;
info.FeatureID = NVSDK_NGX_Feature_SuperSampling;
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<unsigned int>(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<state *>(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<unsigned int>(self.wanted.preset));
}
NVSDK_NGX_DLSS_Create_Params create{};
create.Feature.InWidth = static_cast<unsigned int>(self.wanted.create.x);
create.Feature.InHeight = static_cast<unsigned int>(self.wanted.create.y);
create.Feature.InTargetWidth = static_cast<unsigned int>(self.wanted.output.x);
create.Feature.InTargetHeight = static_cast<unsigned int>(self.wanted.output.y);
create.Feature.InPerfQualityValue = static_cast<NVSDK_NGX_PerfQuality_Value>(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<float>(self.colour.width);
evaluation.InMVScaleY = static_cast<float>(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;
}
}
}
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<state>()} {}
dlss::~dlss()
{
shutdown();
}
bool dlss::compiled()
{
return true;
}
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))
{
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);
}
}
void dlss::device_extensions(VkInstance const Instance, VkPhysicalDevice const Physical, 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_GetFeatureDeviceExtensionRequirements(Instance, Physical, &info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
{
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);
}
}
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;
return true;
}
void dlss::shutdown()
{
auto &s{*m_state};
if (false == s.initialized)
{
return;
}
// the gpu is idle: the feature goes at once
s.release(nullptr, 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<unsigned int>(Output.x), static_cast<unsigned int>(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<int>(minwidth), static_cast<int>(minheight)};
glm::ivec2 const high{static_cast<int>(std::max(maxwidth, minwidth)), static_cast<int>(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<float>(optimalwidth) / static_cast<float>(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<float>(low.x) / static_cast<float>(Output.x), static_cast<float>(low.y) / static_cast<float>(Output.y))};
auto const highscale{std::min(static_cast<float>(high.x) / static_cast<float>(Output.x), static_cast<float>(high.y) / static_cast<float>(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<int>(optimalwidth), static_cast<int>(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<float>(s.plan_render.x) / static_cast<float>(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<float>(s.plan_output.x) / static_cast<float>(s.plan_render.x)};
return static_cast<std::uint32_t>(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<int>(Inputs.colour->desc().extent.width), static_cast<int>(Inputs.colour->desc().extent.height)};
feature.output = {static_cast<int>(Inputs.output->desc().extent.width), static_cast<int>(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<int>(NVSDK_NGX_PerfQuality_Value_DLAA) : planned ? s.plan_mode : static_cast<int>(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::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;
if ((false == s.failed) || s.error_told)
{
return {};
}
s.error_told = true;
return s.error;
}
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";
}
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;
}
}
#else // without the sdk: dlss is never available
struct dlss::state
{
};
dlss::dlss() : m_state{std::make_unique<state>()} {}
dlss::~dlss() = default;
bool dlss::compiled()
{
return false;
}
void dlss::instance_extensions(std::vector<std::string> &) {}
void dlss::device_extensions(VkInstance, VkPhysicalDevice, std::vector<std::string> &) {}
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::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

View File

@@ -0,0 +1,93 @@
/*
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/.
*/
// nvidia dlss (the super resolution of the ngx sdk, built with WITH_DLSS) of the main viewport, gfx.antialiasing dlss: the colour of
// the scene with its depth and velocities, drawn at the size gfx.buffer.percent gives, anti-aliased and upscaled to the size of the
// window. dlaa is the same at the full size. the sdk isn't part of the repository (tools/fetch_dlss_sdk.cmake, DLSS_SDK_ROOT); without
// it dlss is never available and the renderer takes taa
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include <glm/glm.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace vk14
{
class command_stream;
class image;
class deletion_queue;
class dlss
{
public:
// what a frame gives it, the images in the states the evaluation takes (the inputs read by the shaders, the output a storage
// image in the general layout)
struct frame_inputs
{
image *colour{nullptr}; // hdr, linear, before the exposure
image *depth{nullptr}; // reverse z
image *motion{nullptr}; // the velocities to the last frame, in parts of the image (uv), without the offset of the frame
image *exposure{nullptr}; // 1x1, the exposure the tonemapping applies; null: dlss works it out itself
image *output{nullptr}; // the size of the window
// the offset of the projection of the frame in pixels of the colour, along its columns and rows
glm::vec2 jitter{0.f};
bool reset{false}; // the last frames have nothing to do with this one
float frame_time{0.f}; // milliseconds
};
dlss();
~dlss();
dlss(dlss const &) = delete;
dlss &operator=(dlss const &) = delete;
// the program was built with the sdk
static bool compiled();
// the extensions of the instance and of the device ngx needs; asked for before the instance and the device are made, they are
// taken where the driver has them
static void instance_extensions(std::vector<std::string> &Extensions);
static void device_extensions(VkInstance Instance, VkPhysicalDevice Physical, std::vector<std::string> &Extensions);
// ngx on the device, and whether the driver and the gpu give dlss. false: Reason tells why
bool init(VkInstance Instance, VkPhysicalDevice Physical, VkDevice Device, std::string &Reason);
// the gpu is done with everything (the device is idle)
void shutdown();
bool available() const;
// the size the scene is drawn at for the window of the size Output and the part Scale of it (1: dlaa), kept within what dlss takes
// for the mode it picks for them. false: dlss can't upscale to Output
bool plan(glm::ivec2 const &Output, float Scale, glm::ivec2 &Render);
// for the sizes planned: the bias of the mipmaps of the textures of the scene, the number of the offsets the jitter goes through
float lod_bias() const;
std::uint32_t jitter_phases() const;
// the evaluation recorded to Commands (the replay does it on the command buffer of the frame). the feature is made at the first
// one and again when the sizes or the settings change; the old one goes once the gpu is past Value of the timeline
void evaluate(command_stream &Commands, frame_inputs const &Inputs, deletion_queue &Deletions, std::uint64_t Value);
// what the replay of the last frame left (the feature made, an error) taken over for the main thread, once the replay is done
// (after the wait for the render thread); what went wrong, the first time, or empty
std::string sync();
// the feature couldn't be made or evaluated: dlss stays off from then on (as of the last sync())
bool failed() const;
// the mode and the sizes, for the statistics (as of the last sync())
std::string info() const;
// the part of the debug window
void draw_debug_ui();
struct state;
private:
std::unique_ptr<state> m_state;
};
} // namespace vk14

View File

@@ -265,20 +265,28 @@ void vulkan14_renderer::init_frame_data()
{
WriteLog("using multisampling x" + std::to_string(1 << Global.iMultisampling));
}
if (Global.gfx_framebuffer_width == -1)
// the image of the main window: a part of the window (gfx.buffer.percent) stretched to it, or with dlss the size it planned,
// upscaled to the window
if (m_dlssrender.x > 0)
{
Global.gfx_framebuffer_width = Global.fb_size.x;
Global.gfx_framebuffer_width = m_dlssrender.x;
Global.gfx_framebuffer_height = m_dlssrender.y;
}
if (Global.gfx_framebuffer_height == -1)
else if ((Global.gfx_framebuffer_width == -1) || (Global.gfx_framebuffer_height == -1))
{
Global.gfx_framebuffer_height = Global.fb_size.y;
auto const size{Global.gfx_buffer_size()};
Global.gfx_framebuffer_width = size.x;
Global.gfx_framebuffer_height = size.y;
}
WriteLog("main window size: " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height));
WriteLog("main window size: " + std::to_string(Global.fb_size.x) + "x" + std::to_string(Global.fb_size.y) + ", drawn at " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height) +
(m_dlssrender.x > 0 ? " for dlss" : ""));
m_viewports.clear();
auto &main{m_viewports.emplace_back()};
main.width = Global.gfx_framebuffer_width;
main.height = Global.gfx_framebuffer_height;
main.output_width = (m_dlssrender.x > 0 ? m_dlssoutput.x : 0);
main.output_height = (m_dlssrender.x > 0 ? m_dlssoutput.y : 0);
main.main = true;
main.shadow = true;
main.window = 0;
@@ -310,10 +318,10 @@ namespace
{
bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::uint32_t &Formatscount, VkSampleCountFlags const Samplecounts, VkFormat const Bloomformat, VkFormat const Historyformat,
vk14::viewport_targets &Targets, int const Width, int const Height, bool const Srgb, bool const Taa, bool const Reflections)
vk14::viewport_targets &Targets, int const Width, int const Height, int const OutputWidth, int const OutputHeight, bool const Srgb, bool const Taa, bool const Reflections, bool const Dlss)
{
// as many samples as asked for, as far as the device has them for the colour and the depth
auto samples{static_cast<VkSampleCountFlagBits>(std::clamp(1 << Global.iMultisampling, 1, 8))};
// as many samples as asked for, as far as the device has them for the colour and the depth; one for dlss
auto samples{static_cast<VkSampleCountFlagBits>(Dlss ? 1 : std::clamp(1 << Global.iMultisampling, 1, 8))};
while ((samples > VK_SAMPLE_COUNT_1_BIT) && (0 == (Samplecounts & samples)))
{
samples = static_cast<VkSampleCountFlagBits>(samples >> 1);
@@ -321,8 +329,10 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
auto const motion{Global.gfx_motion_vectors()};
auto const aberration{Global.gfx_postfx_chromaticaberration_enabled};
VkExtent2D const extent{static_cast<std::uint32_t>(std::max(Width, 1)), static_cast<std::uint32_t>(std::max(Height, 1))};
if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.samples == samples) && (Targets.motion == motion) && (Targets.taa == Taa) && (Targets.aberration == aberration) &&
(Targets.srgb == Srgb) && (Targets.reflections == Reflections) && Targets.output)
// the images after the upscaling of dlss have the size of the window, the others that of the scene
VkExtent2D const outputextent{Dlss ? VkExtent2D{static_cast<std::uint32_t>(std::max(OutputWidth, 1)), static_cast<std::uint32_t>(std::max(OutputHeight, 1))} : extent};
if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.output_extent.width == outputextent.width) && (Targets.output_extent.height == outputextent.height) &&
(Targets.samples == samples) && (Targets.motion == motion) && (Targets.taa == Taa) && (Targets.dlss == Dlss) && (Targets.aberration == aberration) && (Targets.srgb == Srgb) && (Targets.reflections == Reflections) && Targets.output)
{
return true;
}
@@ -333,6 +343,8 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
auto const olddepth{Targets.depth_formats};
Targets = vk14::viewport_targets{};
Targets.extent = extent;
Targets.output_extent = outputextent;
Targets.dlss = Dlss;
Targets.samples = samples;
Targets.motion = motion;
Targets.taa = Taa;
@@ -340,10 +352,10 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
Targets.srgb = Srgb;
Targets.reflections = Reflections;
auto make = [&](std::shared_ptr<vk14::image> &Image, VkFormat Format, VkImageUsageFlags Usage, VkSampleCountFlagBits Samples, char const *Name) {
auto make = [&](std::shared_ptr<vk14::image> &Image, VkFormat Format, VkImageUsageFlags Usage, VkSampleCountFlagBits Samples, char const *Name, VkExtent2D const Size = VkExtent2D{0, 0}) {
vk14::image_desc desc;
desc.format = Format;
desc.extent = {extent.width, extent.height, 1};
desc.extent = {Size.width > 0 ? Size.width : extent.width, Size.height > 0 ? Size.height : extent.height, 1};
desc.samples = Samples;
desc.usage = Usage;
Image = std::make_shared<vk14::image>();
@@ -377,7 +389,7 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
}
if (motion && Global.gfx_postfx_motionblur_enabled)
{
ok = ok && make(Targets.blurred, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "motion blurred");
ok = ok && make(Targets.blurred, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "motion blurred", outputextent);
}
if (Taa)
{
@@ -402,9 +414,16 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
auto const output{Srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
if (aberration)
{
ok = ok && make(Targets.tonemapped, output, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "tonemapped");
ok = ok && make(Targets.tonemapped, output, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "tonemapped", outputextent);
}
ok = ok && make(Targets.output, output, sampledcolour | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "output", outputextent);
if (Dlss)
{
// what dlss takes besides the scene, and its output, which the post-processing reads (and the history of the reflections is
// copied from)
ok = ok && make(Targets.dlss_motion, velocity, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "dlss velocity") && make(Targets.dlss_exposure, VK_FORMAT_R32_SFLOAT, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "dlss exposure", VkExtent2D{1, 1}) &&
make(Targets.dlss_output, hdr, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | historysource, VK_SAMPLE_COUNT_1_BIT, "dlss output", outputextent);
}
ok = ok && make(Targets.output, output, sampledcolour | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "output");
// the ambient occlusion, half the size, when it's on
if (ok && (Global.gfx_postfx_ao != 0))
{
@@ -431,7 +450,7 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
// the bloom chain, made whether the bloom is on or not: it can be switched on in the debug window
if (ok)
{
auto const sizes{gfx::exposure_settings::bloom_sizes(static_cast<int>(extent.width), static_cast<int>(extent.height))};
auto const sizes{gfx::exposure_settings::bloom_sizes(static_cast<int>(outputextent.width), static_cast<int>(outputextent.height))};
auto makelevel = [&](std::vector<std::shared_ptr<vk14::image>> &Levels, glm::ivec2 const &Size, char const *Name) {
vk14::image_desc desc;
desc.format = Bloomformat;
@@ -516,6 +535,11 @@ bool vulkan14_renderer::Render()
report_device_lost(failure);
return false;
}
// what the replay left to dlss; failed: off from now on, the scene of the main window stretched to it without anti-aliasing
if (auto const error{ctx.dlss.sync()}; false == error.empty())
{
vk14::log_error(error + "; dlss is off, the scene is stretched to the window without anti-aliasing");
}
// the gpu has to be done with the frame which used the slot before
if (frame.done > 0)
@@ -816,7 +840,7 @@ bool vulkan14_renderer::Render()
{
scene.screenshot_wanted = false;
auto &output{*scene.targets.front().output};
auto const extent{scene.targets.front().extent};
VkExtent2D const extent{output.desc().extent.width, output.desc().extent.height};
scene.screenshot = std::make_shared<vk14::buffer>();
if (scene.screenshot->create(ctx.memory, static_cast<VkDeviceSize>(extent.width) * extent.height * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::readback, "screenshot"))
{
@@ -914,17 +938,20 @@ void vulkan14_renderer::render_scene()
auto const &viewport{m_viewports[i]};
auto const srgb{false == Global.gfx_shadergamma};
auto const *history{scene.targets[i].taa_history[0].get()};
auto const *upscaled{scene.targets[i].dlss_output.get()};
auto const *reflections{scene.targets[i].ssr_history.get()};
auto const dlss{viewport.main && dlss_active() && (viewport.output_width > 0) && (viewport.output_height > 0)};
if (false == make_targets(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scene.formats_count, ctx.device.caps().limits.framebufferColorSampleCounts & ctx.device.caps().limits.framebufferDepthSampleCounts, scene.bloom_format,
scene.history_format, scene.targets[i], viewport.width, viewport.height, srgb, viewport.main && Global.gfx_taa(),
viewport.main && (false == Global.gfx_skippipeline) && ((Global.gfx_ssr > 0) || (m_raytracingon && scene.raytracing))))
scene.history_format, scene.targets[i], viewport.width, viewport.height, viewport.output_width, viewport.output_height, srgb, viewport.main && Global.gfx_taa(),
viewport.main && (false == Global.gfx_skippipeline) && ((Global.gfx_ssr > 0) || (m_raytracingon && scene.raytracing)), dlss))
{
continue;
}
if (viewport.main && (scene.targets[i].taa_history[0].get() != history))
if (viewport.main && ((scene.targets[i].taa_history[0].get() != history) || (scene.targets[i].dlss_output.get() != upscaled)))
{
// new images of the history, nothing in them yet
m_taa.reset();
m_dlssreset = true;
}
auto &targets{scene.targets[i]};
if (targets.ssr_history.get() != reflections)
@@ -1284,12 +1311,15 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
m_current_viewport = &vp;
setup_pass(vp, m_renderpass, rendermode::color);
// the temporal anti-aliasing: the projection of the frame moved by a part of a pixel, the velocities pointed to the last frame
auto const taa{vp.main && targets.taa && Global.gfx_taa()};
// the temporal anti-aliasing or dlss: the projection of the frame moved by a part of a pixel, the velocities pointed to the last
// frame. dlss goes through more offsets the smaller the scene is drawn
auto const dlss{vp.main && targets.dlss && targets.dlss_output && dlss_active()};
auto const taa{(vp.main && targets.taa && Global.gfx_taa()) || dlss};
m_motionhistory = taa;
m_cabfuture = glm::mat4{1.f};
if (taa)
{
m_taa.set_phases(dlss ? m_context->dlss.jitter_phases() : 8);
auto const *cab{((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) ? simulation::Train->Dynamic() : nullptr};
m_renderpass.pass_camera.projection() = m_taa.begin_frame(
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height});
@@ -3005,6 +3035,57 @@ void vulkan14_renderer::post_pass(char const *Fragment, vk14::image &Target, std
commands->end_rendering();
}
// dlss of the main viewport (gfx.antialiasing dlss): its inputs made (the velocities of the sky, which has none drawn, from the
// motion of the camera; the exposure of the tonemapping in a texel), then its evaluation into the output of the size of the window
void vulkan14_renderer::render_dlss(std::size_t const Index)
{
auto &ctx{*m_context};
auto &targets{ctx.scene.targets[Index]};
vk14::draw_constants motion;
auto const &reprojection{m_taa.reprojection()};
for (int column = 0; column < 4; ++column)
{
motion.data[2 + column] = reprojection[column];
}
post_pass("postfx_dlss_motion.frag", *targets.dlss_motion, {targets.motion_image.get(), targets.depth.get()}, motion, "dlss inputs");
vk14::image *exposure{nullptr};
if (false == Global.gfx_dlss_autoexposure)
{
vk14::draw_constants constants;
constants.data[0] = gfx::exposure_settings::tonemap_params(0);
post_pass("postfx_dlss_exposure.frag", *targets.dlss_exposure, {targets.colour.get()}, constants);
exposure = targets.dlss_exposure.get();
}
// the inputs read and the output written by the passes of the library, whichever stages they are
auto const stage{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
std::vector<vk14::image_use> uses{vk14::use_of(*targets.colour, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, stage, VK_ACCESS_2_SHADER_READ_BIT),
vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, stage, VK_ACCESS_2_SHADER_READ_BIT),
vk14::use_of(*targets.dlss_motion, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, stage, VK_ACCESS_2_SHADER_READ_BIT),
vk14::use_of(*targets.dlss_output, VK_IMAGE_LAYOUT_GENERAL, stage, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT)};
if (exposure != nullptr)
{
uses.push_back(vk14::use_of(*exposure, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, stage, VK_ACCESS_2_SHADER_READ_BIT));
}
ctx.passes.begin_pass("dlss", uses);
vk14::dlss::frame_inputs inputs;
inputs.colour = targets.colour.get();
inputs.depth = targets.depth.get();
inputs.motion = targets.dlss_motion.get();
inputs.exposure = exposure;
inputs.output = targets.dlss_output.get();
inputs.jitter = m_taa.jitter();
inputs.reset = m_dlssreset || (false == m_taa.history_valid());
inputs.frame_time = static_cast<float>(Timer::GetDeltaRenderTime() * 1000.0);
ctx.dlss.evaluate(*ctx.commands, inputs, ctx.deletions, ctx.uploads.current_value());
m_dlssreset = false;
ctx.passes.end_pass();
}
bool vulkan14_renderer::dlss_active() const
{
return (m_dlssrender.x > 0) && Global.gfx_dlss() && m_context && m_context->dlss.available() && (false == m_context->dlss.failed());
}
// the post-processing of a viewport: from the colour of the scene to its output, which the window shows (the part of
// opengl46_renderer::Render_pass() after the colour pass)
void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionblur)
@@ -3023,6 +3104,12 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
render_ao(Index, aomode);
}
auto *hdr{targets.colour.get()};
// dlss: the scene anti-aliased and upscaled to the window, into the images the rest of the post-processing reads
if (m_motionhistory && targets.dlss && targets.dlss_output && targets.dlss_motion && targets.motion_image && dlss_active())
{
render_dlss(Index);
hdr = targets.dlss_output.get();
}
// the temporal anti-aliasing: the scene blended into its history (the motion blur takes the result)
if (m_motionhistory && targets.taa && targets.taa_history[0] && targets.taa_history[1] && targets.motion_image)
{
@@ -3076,8 +3163,9 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
{
post_pass("postfx_tonemapping.frag", *targets.output, tonemapinput, tonemap, "tonemapping, output");
}
// the lights of the clusters of the pixels over the image, for the debugging of the lights
if (Global.gfx_lights_clusterheatmap)
// the lights of the clusters of the pixels over the image, for the debugging of the lights (the clusters are those of the size the
// scene is drawn at, which dlss upscales)
if (Global.gfx_lights_clusterheatmap && (false == targets.dlss))
{
render_cluster_heatmap(Index);
}

View File

@@ -299,6 +299,22 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
{
Extensions.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
}
// what a library asks for; without one of them the library says it can't start
for (auto const &name : Config.wanted_extensions)
{
if (contains(Extensions, name.c_str()))
{
continue;
}
if (has_extension(available, name.c_str()))
{
Extensions.push_back(name.c_str());
}
else
{
log_warning("the instance extension " + name + " asked for isn't available");
}
}
std::string list;
for (auto const *name : Extensions)

View File

@@ -26,6 +26,8 @@ struct instance_config
int validation{0};
// VK_EXT_debug_utils: the messages of the validation layer in the log, names of objects
bool debug_utils{false};
// the extensions a library asks for (dlss), taken where the loader has them
std::vector<std::string> wanted_extensions;
};
class instance

View File

@@ -26,6 +26,9 @@ namespace
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
// VkPhysicalDeviceProperties::vendorID of nvidia, the gpus dlss runs on
std::uint32_t constexpr nvidia_vendor_id{0x10de};
// the ring each frame in flight streams its data through: the user interface, the blocks of the scene and of the light clusters,
// the light buffers of the passes, the vertices of the particles and the sky; the descriptors read the light buffers with the range
// of the whole gl::light_data, which the tail past the last frame leaves room for
@@ -332,7 +335,7 @@ bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::st
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason, lod_bias))
{
return false;
}
@@ -404,6 +407,8 @@ void vulkan14_renderer::context::destroy()
uploads = vk14::uploader{};
images.clear();
deletions.flush();
// the features of dlss released by the deletions above, ngx goes before the device
dlss.shutdown();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
@@ -494,6 +499,34 @@ gfx_backend_desc vulkan14_renderer::backend_desc()
bool vulkan14_renderer::renderer_register = gfx_renderer_factory::get_instance()->register_backend("vulkan", vulkan14_renderer::backend_desc());
// dlss on the device made for it, and the size the scene of the main window is drawn at; where it can't be had, taa instead
void vulkan14_renderer::start_dlss()
{
auto &ctx{*m_context};
std::string reason;
glm::ivec2 render{0};
auto const output{glm::max(Global.fb_size, glm::ivec2(1))};
if (ctx.device.caps().vendor_id != nvidia_vendor_id)
{
reason = ctx.device.caps().device_name + " isn't an nvidia gpu";
}
else if (ctx.dlss.init(ctx.instance.handle(), ctx.device.physical(), ctx.device.handle(), reason))
{
if (ctx.dlss.plan(output, Global.gfx_dlss_native ? 1.f : Global.gfx_buffer_scale, render))
{
m_dlssrender = render;
m_dlssoutput = output;
ctx.lod_bias = ctx.dlss.lod_bias();
vk14::log_info("dlss: the bias of the mipmaps of the scene " + std::to_string(ctx.lod_bias) + ", " + std::to_string(ctx.dlss.jitter_phases()) + " offsets of the jitter");
return;
}
reason = "it doesn't upscale to the window of " + std::to_string(output.x) + "x" + std::to_string(output.y);
}
ctx.dlss.shutdown();
vk14::log_info("gfx.antialiasing dlss: " + reason + ", using taa");
Global.gfx_antialiasing = global_settings::antialiasing_mode::taa;
}
bool vulkan14_renderer::Init(GLFWwindow *Window)
{
m_context = std::make_unique<context>();
@@ -504,9 +537,30 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
config.validation = Global.gfx_vulkan_validation;
config.debug_utils = (Global.gfx_vulkan_validation > 0);
// dlss (gfx.antialiasing dlss): the extensions ngx needs go into the instance and the device; where it can't be had, taa
if (Global.gfx_dlss() && (false == vk14::dlss::compiled()))
{
vk14::log_info("gfx.antialiasing dlss: this build is without the dlss sdk (WITH_DLSS), using taa");
Global.gfx_antialiasing = global_settings::antialiasing_mode::taa;
}
if (Global.gfx_dlss())
{
vk14::dlss::instance_extensions(config.wanted_extensions);
}
auto const framesinflight{static_cast<std::uint32_t>(std::clamp(Global.gfx_vulkan_framesinflight, 2, 3))};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason) &&
ctx.create_core(framesinflight, reason)};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason)};
std::vector<std::string> deviceextensions;
if (ok && Global.gfx_dlss() && (ctx.device.caps().vendor_id == nvidia_vendor_id))
{
vk14::dlss::device_extensions(ctx.instance.handle(), ctx.device.physical(), deviceextensions);
}
ok = ok && ctx.device.create(ctx.instance, reason, deviceextensions);
if (ok && Global.gfx_dlss())
{
start_dlss();
}
ok = ok && ctx.create_frames(framesinflight, reason) && ctx.create_core(framesinflight, reason);
if (ok)
{
auto mainwindow{std::make_unique<vk14::swapchain>()};
@@ -581,6 +635,13 @@ bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const
viewport = &m_viewports.back();
viewport->window = ctx.windows.size() - 1;
}
if (viewport->main && (viewport->output_width > 0))
{
// the size dlss was planned for isn't the one of the viewport: the main window without it
vk14::log_info("dlss: off for the main window, drawn by the viewport " + conf.monitor + " at a size of its own");
viewport->output_width = 0;
viewport->output_height = 0;
}
viewport->width = conf.width;
viewport->height = conf.height;
viewport->projection = conf.projection;
@@ -764,6 +825,11 @@ void vulkan14_renderer::update_info()
auto const extent{window->extent()};
m_infostats += "window: " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + (window->srgb() ? ", srgb" : ", unorm") + (window->present_mode() == VK_PRESENT_MODE_FIFO_KHR ? ", vsync" : ", no vsync") + "\n";
}
if (false == m_viewports.empty())
{
auto const &main{m_viewports.front()};
m_infostats += "scene drawn at " + std::to_string(main.width) + "x" + std::to_string(main.height) + (dlss_active() ? ", " + ctx.dlss.info() : std::string{}) + "\n";
}
// the statistics of the scene and of the cullings, as the opengl renderer gives them
collect_cluster_stats();
m_infostats += "\n" + scene_info() + cull_info();

View File

@@ -133,8 +133,12 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// another projection)
struct viewport_config
{
// the size the scene is drawn at
int width{0};
int height{0};
// the size of the image after the upscaling of dlss (the window); 0: the size above
int output_width{0};
int output_height{0};
float draw_range{1.f};
bool main{false};
bool shadow{false}; // its view is taken into the sun shadows
@@ -297,6 +301,7 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
void render_editor_overlay();
// the post-processing of a viewport: the motion blur, the tonemapping, the chromatic aberration
void render_post(std::size_t const Viewport, bool const Motionblur);
void render_dlss(std::size_t const Viewport);
// the output of a viewport to its window, with the user interface over it for the main window
void present_viewport(std::size_t const Viewport);
// the light buffer of the pass: the sun, the ambient light, the fog, the headlights, the lamps (the clustered lights)
@@ -427,8 +432,16 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
double m_environmentupdatetime{0.0};
glm::dvec3 m_environmentupdatelocation{0.0};
bool m_vehiclesmoved{true};
// the temporal anti-aliasing of the main viewport (gfx.antialiasing taa)
// the temporal anti-aliasing of the main viewport (gfx.antialiasing taa), whose jitter and history dlss takes too
gfx::temporal_aa m_taa;
// dlss of the main viewport (gfx.antialiasing dlss): the size the scene is drawn at and the window it's upscaled to, planned at
// the start (0: dlss is off); the next frame starts its history anew
glm::ivec2 m_dlssrender{0};
glm::ivec2 m_dlssoutput{0};
bool m_dlssreset{true};
void start_dlss();
// dlss is used for the main viewport this frame
bool dlss_active() const;
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};

View File

@@ -630,6 +630,13 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
{
scene.post_fragments.emplace("postfx_taa.frag", ctx.shaders.get("postfx_taa.frag"));
}
if (Global.gfx_dlss())
{
for (auto const *name : {"postfx_dlss_motion.frag", "postfx_dlss_exposure.frag"})
{
scene.post_fragments.emplace(name, ctx.shaders.get(name));
}
}
auto const build{ctx.shaders.get("cluster_build.comp")};
auto const cull{ctx.shaders.get("cluster_cull.comp")};
scene.cluster_build = ctx.pipelines.compute(build, scene.cluster_layout, {}, "cluster build");