17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 20:34:54 +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

2
.gitignore vendored
View File

@@ -21,6 +21,8 @@ cmake-build-*/
# Vcpkg manifest
vcpkg.json
/ref/crashpad
# the NVIDIA DLSS SDK, fetched by tools/fetch_dlss_sdk.cmake (WITH_DLSS)
/ref/DLSS/
# Local Python (setup.bat / CMake) for embedding
tools/python314/

View File

@@ -72,6 +72,7 @@ endif()
option(WITH_OPENGL_MODERN "Compile with OpenGL modern renderer" ON)
option(WITH_VULKAN "Compile with the Vulkan 1.4 renderer (gfxrenderer vulkan); needs the submodules ref/volk and ref/Vulkan-Headers" ON)
option(WITH_DLSS "Compile the Vulkan renderer with NVIDIA DLSS (gfx.antialiasing dlss); needs the DLSS SDK in DLSS_SDK_ROOT, cmake -P tools/fetch_dlss_sdk.cmake" ON)
option(WITH_PYTHON "Python 3.14 embedded scripting" ON)
option(WITH_UART "Compile with libserialport" ON)
option(WITH_LUA "Compile with lua scripting support" ON)
@@ -463,9 +464,54 @@ if (WITH_VULKAN)
"rendering/vulkan14/vulkan14debug.cpp"
"rendering/vulkan14/vulkan14commands.cpp"
"rendering/vulkan14/vulkan14raytracing.cpp"
"rendering/vulkan14/vulkan14dlss.cpp"
"ref/volk/volk.c")
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")
# nvidia dlss of the vulkan renderer, from the dlss sdk (github.com/NVIDIA/DLSS) outside the repository: the headers and the
# static library of ngx to build with, the dlss library next to the program to run with (rendering/vulkan14/vulkan14dlss.h)
if (WITH_DLSS)
set(DLSS_SDK_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/ref/DLSS" CACHE PATH "The NVIDIA DLSS SDK, with include/ and lib/ (cmake -P tools/fetch_dlss_sdk.cmake)")
if (NOT EXISTS "${DLSS_SDK_ROOT}/include/nvsdk_ngx_helpers_vk.h")
message(FATAL_ERROR "WITH_DLSS=ON needs the DLSS SDK in DLSS_SDK_ROOT (${DLSS_SDK_ROOT}): cmake -P tools/fetch_dlss_sdk.cmake")
endif()
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(DLSS_ARCH "aarch64")
else()
set(DLSS_ARCH "x86_64")
endif()
if (WIN32)
# the variants of the dynamic c runtime (/MD, /MDd in debug)
# the static libraries of x86_64 are in x64/ (the others of older visual studios beside it), those of arm64 at the top
if (DLSS_ARCH STREQUAL "x86_64")
set(DLSS_NGX_DIR "${DLSS_SDK_ROOT}/lib/Windows_x86_64/x64")
else()
set(DLSS_NGX_DIR "${DLSS_SDK_ROOT}/lib/Windows_${DLSS_ARCH}")
endif()
set(DLSS_NGX_LIBRARY "$<IF:$<CONFIG:Debug>,${DLSS_NGX_DIR}/nvsdk_ngx_d_dbg.lib,${DLSS_NGX_DIR}/nvsdk_ngx_d.lib>")
set(DLSS_NGX_CHECK "${DLSS_NGX_DIR}/nvsdk_ngx_d.lib")
set(DLSS_RUNTIME_PATTERN "nvngx_dlss.dll")
set(DLSS_RUNTIME_DIR "${DLSS_SDK_ROOT}/lib/Windows_${DLSS_ARCH}")
elseif (UNIX AND NOT APPLE)
set(DLSS_NGX_LIBRARY "${DLSS_SDK_ROOT}/lib/Linux_${DLSS_ARCH}/libnvsdk_ngx.a")
set(DLSS_NGX_CHECK "${DLSS_NGX_LIBRARY}")
set(DLSS_RUNTIME_PATTERN "libnvidia-ngx-dlss.so.*")
set(DLSS_RUNTIME_DIR "${DLSS_SDK_ROOT}/lib/Linux_${DLSS_ARCH}")
else()
message(FATAL_ERROR "WITH_DLSS=ON: the DLSS SDK has no libraries for this platform")
endif()
if (NOT EXISTS "${DLSS_NGX_CHECK}")
message(FATAL_ERROR "WITH_DLSS=ON: ${DLSS_NGX_CHECK} isn't in the DLSS SDK, fetch the libraries of this platform: cmake -P tools/fetch_dlss_sdk.cmake")
endif()
file(GLOB DLSS_RUNTIME_RELEASE "${DLSS_RUNTIME_DIR}/rel/${DLSS_RUNTIME_PATTERN}")
file(GLOB DLSS_RUNTIME_DEVELOPMENT "${DLSS_RUNTIME_DIR}/dev/${DLSS_RUNTIME_PATTERN}")
if (NOT DLSS_RUNTIME_RELEASE)
message(WARNING "WITH_DLSS=ON: no ${DLSS_RUNTIME_PATTERN} in ${DLSS_RUNTIME_DIR}/rel, it has to be put next to the program by hand")
endif()
# the sdk is seen by vulkan14dlss.cpp only; the rest of the renderer asks vk14::dlss::compiled()
set_source_files_properties("rendering/vulkan14/vulkan14dlss.cpp" PROPERTIES COMPILE_DEFINITIONS "WITH_DLSS" SKIP_PRECOMPILE_HEADERS ON)
endif()
# glslang compiles the shaders of shaders/vk14 when the program runs (no spirv-tools, no hlsl, no programs of its own)
set(GLSLANG_TESTS OFF CACHE BOOL "" FORCE)
set(GLSLANG_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
@@ -606,6 +652,20 @@ if (WITH_VULKAN)
# the vulkan headers are seen by the files of rendering/vulkan14 only, they include them
target_include_directories(${PROJECT_NAME} PRIVATE "${VULKAN_HEADERS_DIR}" "${VOLK_DIR}" "${VMA_DIR}")
target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS} glslang glslang-default-resource-limits)
if (WITH_DLSS)
target_include_directories(${PROJECT_NAME} PRIVATE "${DLSS_SDK_ROOT}/include")
target_link_libraries(${PROJECT_NAME} "${DLSS_NGX_LIBRARY}" ${CMAKE_DL_LIBS})
# the dlss library next to the program, the development one (gfx.dlss.developer) in ngx_dev/
if (DLSS_RUNTIME_RELEASE)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DLSS_RUNTIME_RELEASE} "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
endif()
if (DLSS_RUNTIME_DEVELOPMENT)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:${PROJECT_NAME}>/ngx_dev"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DLSS_RUNTIME_DEVELOPMENT} "$<TARGET_FILE_DIR:${PROJECT_NAME}>/ngx_dev")
endif()
endif()
endif()
target_compile_definitions(eu07_settings INTERFACE ${DEFINITIONS})

View File

@@ -91,9 +91,27 @@ anti-aliasing with it. The velocities are drawn with it whether the motion blur
motion blur smears the image after the TAA, and it also blurs the turning of the camera. Thin lines across the
pixels (rails and wires far away) are locked in the history for 8 frames on the world standing still farther
than 20..40 m, so the frames whose offset misses them don't make them flicker; on near or fast moving pixels the
usual check of the history against the colours around the pixel applies. Further anti-aliasing modes (FSR, DLSS) are meant to be added to
usual check of the history against the colours around the pixel applies. Further anti-aliasing modes (FSR) are meant to be added to
the same entry.
| Entry | Default | Meaning |
|---|---|---|
| `gfx.antialiasing dlss/dlaa` | | NVIDIA DLSS of the Vulkan renderer: the scene of the main window drawn at `gfx.buffer.percent` of the window with one sample, the jitter of `taa` (more offsets the smaller the image: 8 for each pixel of the window) and anti-aliased and upscaled to the window by DLSS. `dlaa` is DLSS at the full size of the window whatever `gfx.buffer.percent` says. Needs a build with `WITH_DLSS`, a GeForce RTX and its driver; otherwise, and with OpenGL, `taa` is taken at the start (the log says why). Should DLSS fail later, the scene is stretched to the window without anti-aliasing (the negative bias of the mipmaps stays until the restart) |
| `gfx.buffer.percent` | `1.0` | the size of the image the scene of the main window is drawn at, as a part of the size of the window (`0.5`: half its width and half its height; a value over 4 is taken as percents). With `dlss` it's kept within what the DLSS mode nearest to it takes (about 0.33..1.0) and upscaled by it; with the other modes the image is stretched to the window. Replaces `gfx.framebuffer.width` and `gfx.framebuffer.height`, which aren't read any more. Read at the start |
| `gfx.dlss.preset default/j/k/l/m` | `default` | the preset of the DLSS model (the DLSS SDK 310 has these; `default` lets the library pick per mode) |
| `gfx.dlss.autoexposure yes/no` | `no` | DLSS works the exposure out itself instead of being given the exposure of the tonemapping |
| `gfx.dlss.lodbias` | `0` | added to the bias of the mipmaps of the textures of the scene with DLSS, `log2(drawn / window) - 1` (less negative against moiré, more for sharper textures) |
| `gfx.dlss.developer yes/no` | `no` | the development library of DLSS (`ngx_dev/` next to the program): its indicator and the debug keys of the DLSS programming guide (Ctrl+Alt+F12 debug views, Ctrl+Alt+F9 jitter configurations) |
DLSS needs the NVIDIA DLSS SDK at build time, which isn't in the repository (it keeps the binaries of every platform in
git): `cmake -P tools/fetch_dlss_sdk.cmake` takes the headers and the libraries of the platform into `ref/DLSS`
(ignored by git), then configure with `-DWITH_DLSS=ON` (`-DDLSS_SDK_ROOT=<dir>` for another place). The build copies
`nvngx_dlss.dll` (Windows) or `libnvidia-ngx-dlss.so.<version>` (Linux) next to the program, where NGX looks for it,
and the development library into `ngx_dev/`; a program copied elsewhere needs the library next to it. The velocities DLSS
is given are those of `taa`, the sky (which has none drawn) gets the motion of the camera; animated parts of models other
than wheels (gauges, levers, wipers) have no velocity of their own yet. The debug window of the Vulkan renderer shows the
mode and the sizes, and can turn the jitter offsets the other way (to check their direction) and drop the history.
### Ambient occlusion
Darkening of creases, corners and the ground under objects, worked out from the depth of the image (screen space)

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");

View File

@@ -0,0 +1,16 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// the exposure the tonemapping applies to the image (postfx_tonemapping.frag), in the texel dlss takes it from
// (rendering/vulkan14/vulkan14dlss.h). param is pc.data of the push constants: param[0].y 1 if the automatic exposure is applied
// (exposure_ssbo of common.glsl), param[0].z the fixed exposure multiplier
layout(location = 0) out vec4 out_exposure;
void main()
{
float scale = param[0].z;
if (param[0].y > 0.5 && exposure > 0.0)
scale *= exposure;
out_exposure = vec4(scale, 0.0, 0.0, 1.0);
}

View File

@@ -0,0 +1,29 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// the velocities dlss takes (rendering/vulkan14/vulkan14dlss.h): those the colour pass drew, and for the sky, which has none drawn
// (the depth of the reverse z at its infinity), the motion of the camera, as the temporal anti-aliasing takes it (taa_resolve.glsl).
// param is pc.data of the push constants: param[2..5] the columns of the matrix of the clip space of this frame to the one of the
// last frame, for the world standing still (gfx::temporal_aa::reprojection())
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_motion;
#pragma texture(velocity_tex, 0, RG)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D velocity_tex;
#pragma texture(depth_tex, 1, R)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D depth_tex;
void main()
{
ivec2 pixel = ivec2(gl_FragCoord.xy);
vec2 velocity = texelFetch(velocity_tex, pixel, 0).xy;
float depth = texelFetch(depth_tex, pixel, 0).r;
if (depth <= 0.0)
{
vec4 world = mat4(param[2], param[3], param[4], param[5]) * vec4(f_coords * 2.0 - 1.0, depth, 1.0);
velocity = (world.w > 0.0) ? (world.xy / world.w * 0.5 + 0.5) - f_coords : vec2(0.0);
}
out_motion = vec4(velocity, 0.0, 0.0);
}

View File

@@ -0,0 +1,82 @@
# fetches the parts of the NVIDIA DLSS SDK (github.com/NVIDIA/DLSS) the Vulkan renderer builds with (WITH_DLSS): the headers, the
# static library of NGX and the DLSS library for the platform. the SDK keeps the binaries of every platform in git (hundreds of MB a
# release), so it isn't a submodule: this takes only the files needed, with a sparse checkout of one tag.
#
# cmake -P tools/fetch_dlss_sdk.cmake [-DDLSS_SDK_ROOT=<dir>] [-DDLSS_SDK_TAG=<tag>] [-DDLSS_SDK_PLATFORMS=windows;linux] [-DDLSS_SDK_ARCH=x86_64|aarch64]
#
# DLSS_SDK_ROOT: where it goes, ref/DLSS by default (ignored by git), which CMakeLists.txt looks at too. DLSS_SDK_PLATFORMS: the
# platforms whose libraries are taken, the one running this by default. run again to change the tag or the platforms.
# the SDK has its own license (LICENSE.txt in it): the DLSS libraries may go out with the program, the SDK itself may not.
cmake_minimum_required(VERSION 3.16)
if(NOT DLSS_SDK_ROOT)
set(DLSS_SDK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../ref/DLSS")
endif()
get_filename_component(DLSS_SDK_ROOT "${DLSS_SDK_ROOT}" ABSOLUTE)
if(NOT DLSS_SDK_TAG)
set(DLSS_SDK_TAG "v310.9.16")
endif()
if(NOT DLSS_SDK_PLATFORMS)
if(CMAKE_HOST_WIN32)
set(DLSS_SDK_PLATFORMS "windows")
else()
set(DLSS_SDK_PLATFORMS "linux")
endif()
endif()
if(NOT DLSS_SDK_ARCH)
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(DLSS_SDK_ARCH "aarch64")
else()
set(DLSS_SDK_ARCH "x86_64")
endif()
endif()
find_package(Git REQUIRED)
# the headers, the license, and for each platform the static library of ngx (the variants of the dynamic c runtime the program
# builds with, /MD and /MDd) with the release and the development library of dlss (the latter shows the indicator and takes the
# debug keys, gfx.dlss.developer)
set(patterns "/include/" "/LICENSE.txt")
foreach(platform IN LISTS DLSS_SDK_PLATFORMS)
if(platform STREQUAL "windows")
# the static libraries of x86_64 are in x64/, those of arm64 at the top of their directory
set(static "/lib/Windows_${DLSS_SDK_ARCH}")
if(DLSS_SDK_ARCH STREQUAL "x86_64")
set(static "${static}/x64")
endif()
list(APPEND patterns
"${static}/nvsdk_ngx_d.lib"
"${static}/nvsdk_ngx_d_dbg.lib"
"/lib/Windows_${DLSS_SDK_ARCH}/rel/nvngx_dlss.dll"
"/lib/Windows_${DLSS_SDK_ARCH}/dev/nvngx_dlss.dll")
elseif(platform STREQUAL "linux")
list(APPEND patterns
"/lib/Linux_${DLSS_SDK_ARCH}/libnvsdk_ngx.a"
"/lib/Linux_${DLSS_SDK_ARCH}/rel/libnvidia-ngx-dlss.so.*"
"/lib/Linux_${DLSS_SDK_ARCH}/dev/libnvidia-ngx-dlss.so.*")
else()
message(FATAL_ERROR "unknown platform \"${platform}\" in DLSS_SDK_PLATFORMS, expected windows or linux")
endif()
endforeach()
function(run_git)
execute_process(COMMAND "${GIT_EXECUTABLE}" ${ARGN} RESULT_VARIABLE result)
if(NOT result EQUAL 0)
message(FATAL_ERROR "git ${ARGN} failed (${result})")
endif()
endfunction()
if(EXISTS "${DLSS_SDK_ROOT}/.git")
message(STATUS "updating the DLSS SDK in ${DLSS_SDK_ROOT} to ${DLSS_SDK_TAG}")
run_git(-C "${DLSS_SDK_ROOT}" fetch --depth 1 --filter=blob:none origin "refs/tags/${DLSS_SDK_TAG}:refs/tags/${DLSS_SDK_TAG}")
run_git(-C "${DLSS_SDK_ROOT}" sparse-checkout set --no-cone ${patterns})
run_git(-C "${DLSS_SDK_ROOT}" checkout --detach "${DLSS_SDK_TAG}")
else()
message(STATUS "fetching the DLSS SDK ${DLSS_SDK_TAG} to ${DLSS_SDK_ROOT}")
run_git(clone --filter=blob:none --no-checkout --depth 1 --branch "${DLSS_SDK_TAG}" https://github.com/NVIDIA/DLSS.git "${DLSS_SDK_ROOT}")
run_git(-C "${DLSS_SDK_ROOT}" sparse-checkout set --no-cone ${patterns})
run_git(-C "${DLSS_SDK_ROOT}" checkout)
endif()
message(STATUS "the DLSS SDK is in ${DLSS_SDK_ROOT}; configure with -DWITH_DLSS=ON")

View File

@@ -77,6 +77,12 @@ void global_settings::FinalizeConfig()
#endif
}
glm::ivec2 global_settings::gfx_buffer_size(float const Scale) const
{
auto const scale{Scale > 0.f ? Scale : gfx_buffer_scale};
return glm::max(glm::ivec2(glm::round(glm::vec2(fb_size) * scale)), glm::ivec2(1));
}
bool global_settings::ConfigParseGeneral(cParser& Parser, const std::string& token)
{
if (token == "sceneryfile")
@@ -1463,15 +1469,24 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1, false);
Parser >> reflectiontune.range_vehicles;
}
else if (Token == "gfx.framebuffer.width")
else if ((Token == "gfx.framebuffer.width") || (Token == "gfx.framebuffer.height"))
{
// the size of the image is a part of the size of the window now (gfx.buffer.percent)
Parser.getTokens(1, false);
Parser >> gfx_framebuffer_width;
int ignored{0};
Parser >> ignored;
WriteLog("eu07.ini: \"" + std::string(Token) + "\" is no longer supported and was ignored, use gfx.buffer.percent");
}
else if (Token == "gfx.framebuffer.height")
else if (Token == "gfx.buffer.percent")
{
// a part of the window: 0.5 is half its width and half its height. a value over 4 is taken as percents (50: 0.5)
Parser.getTokens(1, false);
Parser >> gfx_framebuffer_height;
Parser >> gfx_buffer_scale;
if (gfx_buffer_scale > 4.f)
{
gfx_buffer_scale *= 0.01f;
}
gfx_buffer_scale = std::clamp(gfx_buffer_scale, 0.25f, 2.f);
}
else if (Token == "gfx.shadowmap.enabled")
{
@@ -1914,14 +1929,19 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
Parser >> level;
gfx_fog_quality = ( level == "low" || level == "1" ) ? 1 : ( level == "high" || level == "3" ) ? 3 : ( level == "ultra" || level == "4" ) ? 4 : 2; }
else if( Token == "gfx.antialiasing" ) {
// off, msaax2, msaax4, msaax8 or taa; it sets the multisampling too, the key which used to say it
// off, msaax2, msaax4, msaax8, taa, dlss or dlaa; it sets the multisampling too, the key which used to say it
Parser.getTokens( 1 );
std::string mode;
Parser >> mode;
std::transform( mode.begin(), mode.end(), mode.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
gfx_dlss_native = false;
if( mode == "taa" ) {
gfx_antialiasing = antialiasing_mode::taa;
iMultisampling = 0; }
else if( mode == "dlss" || mode == "dlaa" ) {
gfx_antialiasing = antialiasing_mode::dlss;
gfx_dlss_native = ( mode == "dlaa" );
iMultisampling = 0; }
else if( mode == "msaax2" || mode == "msaax4" || mode == "msaax8" || mode == "msaax16" ) {
gfx_antialiasing = antialiasing_mode::msaa;
iMultisampling = ( mode == "msaax2" ) ? 1 : ( mode == "msaax4" ) ? 2 : ( mode == "msaax8" ) ? 3 : 4; }
@@ -1929,7 +1949,19 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
gfx_antialiasing = antialiasing_mode::off;
iMultisampling = 0; }
else {
ErrorLog( "Bad config: unknown gfx.antialiasing \"" + mode + "\", expected off, msaax2, msaax4, msaax8 or taa" ); } }
ErrorLog( "Bad config: unknown gfx.antialiasing \"" + mode + "\", expected off, msaax2, msaax4, msaax8, taa, dlss or dlaa" ); } }
else if( Token == "gfx.dlss.preset" ) {
// the presets of the dlss sdk 310 (the others are gone, deprecated or reserved)
Parser.getTokens( 1 );
std::string preset;
Parser >> preset;
std::transform( preset.begin(), preset.end(), preset.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
gfx_dlss_preset = ( preset == "j" ) ? 10 : ( preset == "k" ) ? 11 : ( preset == "l" ) ? 12 : ( preset == "m" ) ? 13 : 0;
if( ( gfx_dlss_preset == 0 ) && ( preset != "default" ) ) {
ErrorLog( "Bad config: unknown gfx.dlss.preset \"" + preset + "\", expected default, j, k, l or m" ); } }
else if( Token == "gfx.dlss.autoexposure" ) { readswitch( gfx_dlss_autoexposure ); }
else if( Token == "gfx.dlss.lodbias" ) { readvalue( gfx_dlss_lodbias, -2.f, 2.f ); }
else if( Token == "gfx.dlss.developer" ) { readswitch( gfx_dlss_developer ); }
else if( Token == "gfx.fog.range" ) { readvalue( gfx_fog_range, 100.f, 10000.f ); }
else if( Token == "gfx.fog.lights" ) { readvalue( gfx_fog_lights, 0.f, 10.f ); }
else if( Token == "gfx.fog.shadows" ) { readswitch( gfx_fog_shadows ); }
@@ -2065,7 +2097,12 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.reflections.fidelity", reflectiontune.fidelity );
export_as_text( Output, "timespeed", fTimeSpeed );
export_as_text( Output, "multisampling", iMultisampling );
export_as_text( Output, "gfx.antialiasing", std::string{ gfx_antialiasing == antialiasing_mode::taa ? "taa" : ( gfx_antialiasing == antialiasing_mode::off || iMultisampling == 0 ) ? "off" : "msaax" + std::to_string( 1 << iMultisampling ) } );
export_as_text( Output, "gfx.antialiasing", std::string{ gfx_antialiasing == antialiasing_mode::taa ? "taa" : gfx_antialiasing == antialiasing_mode::dlss ? ( gfx_dlss_native ? "dlaa" : "dlss" ) : ( gfx_antialiasing == antialiasing_mode::off || iMultisampling == 0 ) ? "off" : "msaax" + std::to_string( 1 << iMultisampling ) } );
export_as_text( Output, "gfx.buffer.percent", gfx_buffer_scale );
export_as_text( Output, "gfx.dlss.preset", std::string{ gfx_dlss_preset == 10 ? "j" : gfx_dlss_preset == 11 ? "k" : gfx_dlss_preset == 12 ? "l" : gfx_dlss_preset == 13 ? "m" : "default" } );
export_as_text( Output, "gfx.dlss.autoexposure", gfx_dlss_autoexposure );
export_as_text( Output, "gfx.dlss.lodbias", gfx_dlss_lodbias );
export_as_text( Output, "gfx.dlss.developer", gfx_dlss_developer );
export_as_text( Output, "latitude", fLatitudeDeg );
export_as_text( Output, "convertmodels", iConvertModels );
export_as_text( Output, "file.binary.terrain", file_binary_terrain );
@@ -2188,8 +2225,6 @@ global_settings::export_as_text( std::ostream &Output ) const {
<< extcam_res.y << "\n";
export_as_text( Output, "compresstex", compress_tex );
export_as_text( Output, "gfx.framebuffer.width", gfx_framebuffer_width );
export_as_text( Output, "gfx.framebuffer.height", gfx_framebuffer_height );
export_as_text( Output, "gfx.shadowmap.enabled", gfx_shadowmap_enabled );
// TODO: export fpslimit
export_as_text( Output, "randomseed", Global.random_seed );

View File

@@ -182,12 +182,30 @@ struct global_settings {
GLint iMaxTextureSize{ 4096 }; // maksymalny rozmiar tekstury
GLint iMaxCabTextureSize{ 4096 }; // largest allowed texture in vehicle cab
int iMultisampling{ 2 }; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px
// gfx.antialiasing: off, msaax2/x4/x8 (the samples go to iMultisampling) or taa (one sample, with the history of the last frames;
// iMultisampling 0). the old key multisampling sets msaa or off. read at the start
enum class antialiasing_mode { off, msaa, taa };
// gfx.antialiasing: off, msaax2/x4/x8 (the samples go to iMultisampling), taa (one sample, with the history of the last frames;
// iMultisampling 0) or dlss (nvidia dlss of the vulkan renderer, one sample: the image of gfx.buffer.percent upscaled to the
// window; dlaa is dlss at the full size of the window). the old key multisampling sets msaa or off. read at the start; where dlss
// can't be had at the start (the opengl renderers, no nvidia rtx, a build without WITH_DLSS) the renderer takes taa instead, a
// failure of dlss later leaves the scene stretched to the window without anti-aliasing
enum class antialiasing_mode { off, msaa, taa, dlss };
antialiasing_mode gfx_antialiasing{ antialiasing_mode::msaa };
// the temporal anti-aliasing is on: asked for, and the post-processing which does it is there
bool gfx_taa() const { return ( gfx_antialiasing == antialiasing_mode::taa ) && ( false == gfx_skippipeline ); }
// dlss is on: asked for, with the post-processing (the renderer sets gfx_antialiasing to taa where it can't have it)
bool gfx_dlss() const { return ( gfx_antialiasing == antialiasing_mode::dlss ) && ( false == gfx_skippipeline ); }
// gfx.buffer.percent: the size of the image the scene of the main window is drawn to, as a part of the size of the window
// (0.5: half its width and half its height). dlss upscales it to the window, the other modes stretch it. read at the start
float gfx_buffer_scale{ 1.f };
// gfx.antialiasing dlaa: dlss at the full size of the window whatever gfx.buffer.percent says
bool gfx_dlss_native{ false };
// gfx.dlss.preset: default, j, k, l or m, the value of NVSDK_NGX_DLSS_Hint_Render_Preset (0, 10, 11, 12, 13)
int gfx_dlss_preset{ 0 };
// gfx.dlss.autoexposure: dlss works the exposure out itself instead of taking the one of the tonemapping
bool gfx_dlss_autoexposure{ false };
// gfx.dlss.lodbias: added to the bias of the mipmaps of the textures of the scene dlss is given (log2(render / window) - 1)
float gfx_dlss_lodbias{ 0.f };
// gfx.dlss.developer: the development library of dlss (ngx_dev/ next to the program) with its indicator and debug keys
bool gfx_dlss_developer{ false };
float SplineFidelity{ 1.f }; // determines segment size during conversion of splines to geometry
float SleeperDistance{ 250.f }; // max distance (in meters) at which per-track sleeper models are still drawn; 0 disables sleeper rendering entirely
std::string Sleepers3D; // model of the sleepers and switch timbers laid where the simulator makes the ballast; empty: none
@@ -298,8 +316,13 @@ struct global_settings {
std::chrono::duration<float> python_minframetime {0.01f};
bool gfx_skiprendering = false;
// the size of the image the scene of the main window is drawn to: -1 until the renderer works it out from the window and
// gfx.buffer.percent (the old keys gfx.framebuffer.width and .height aren't taken any more)
int gfx_framebuffer_width = -1;
int gfx_framebuffer_height = -1;
// the size of the image of the main window drawn from the window and gfx.buffer.percent (Scale: the part of the window, 0: that
// of the settings), at least a pixel
glm::ivec2 gfx_buffer_size( float Scale = 0.f ) const;
bool gfx_shadowmap_enabled = true;
bool gfx_envmap_enabled = true;
bool gfx_lights_clustered = true; // headlights from the light lists of the clusters (no limit of lit headlights) or the first dynamiclights of them
@@ -367,8 +390,8 @@ struct global_settings {
// one for each of the last frames, instead of with glBufferSubData. read at the start
bool gfx_bufferstreaming = true;
bool gfx_postfx_motionblur_enabled = true;
// the colour pass writes the velocities of the pixels: for the motion blur, or for the temporal anti-aliasing
bool gfx_motion_vectors() const { return gfx_postfx_motionblur_enabled || gfx_taa(); }
// the colour pass writes the velocities of the pixels: for the motion blur, the temporal anti-aliasing or dlss
bool gfx_motion_vectors() const { return gfx_postfx_motionblur_enabled || gfx_taa() || gfx_dlss(); }
float gfx_postfx_motionblur_shutter = 0.01f;
GLenum gfx_postfx_motionblur_format = GL_RG16F;
GLenum gfx_format_color = GL_RGB16F;