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

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

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

View File

@@ -75,12 +75,14 @@ glm::mat4 gfx::temporal_aa::begin_frame(glm::mat4 const &Projection, glm::dmat3
m_worldfuture = glm::mat4{projectionfix * world};
m_cabfuture = glm::mat4{projectionfix * cab};
m_reprojection = glm::mat4{previous * world * inverseprojection};
m_reprojectionnooffset = glm::mat4{m_previous.projection * world * glm::inverse(current.projection)};
}
else
{
m_worldfuture = glm::mat4{1.f};
m_cabfuture = glm::mat4{1.f};
m_reprojection = glm::mat4{1.f};
m_reprojectionnooffset = glm::mat4{1.f};
}
m_target = 1 - m_target;
m_previous = current;

View File

@@ -65,6 +65,11 @@ class temporal_aa
{
return m_reprojection;
}
// the same without the offsets of the frames (the frame generation of dlss)
glm::mat4 const &reprojection_without_offset() const
{
return m_reprojectionnooffset;
}
// the image of the history written this frame (0 or 1); the other one holds the last frame
int target() const
{
@@ -95,6 +100,7 @@ class temporal_aa
glm::mat4 m_worldfuture{1.f};
glm::mat4 m_cabfuture{1.f};
glm::mat4 m_reprojection{1.f};
glm::mat4 m_reprojectionnooffset{1.f};
int m_target{0};
bool m_historyvalid{false};
};

View File

@@ -158,6 +158,8 @@ public:
virtual auto Camera_Position() const -> glm::dvec3 { return glm::dvec3( 0.0 ); }
// maintenance methods
virtual void Update( double const Deltatime ) = 0;
// the main loop begins a frame, before it reads the input: a renderer keeping the latency low waits here (reflex of vulkan)
virtual void Frame_Start() {}
virtual void Update_Pick_Control() = 0;
virtual void Update_Pick_Node() = 0;
virtual auto Update_Mouse_Position() -> glm::dvec3 = 0;

View File

@@ -40,6 +40,7 @@ http://mozilla.org/MPL/2.0/.
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
@@ -161,6 +162,27 @@ struct viewport_targets
bool srgb{true}; // the output encodes the colour (gfx.shadergamma no)
};
// the images of the frame generation of dlss (gfx.framegeneration) for the main window, made for its size and its format: the frame
// with the user interface (the backbuffer) and without it (the hudless), the frames generated, the size of the window; the depth
// and the velocities turned over (postfx_fg_flip.frag), the size of the scene. all of them storage images (ngx takes them so)
struct generation_targets
{
VkExtent2D extent{0, 0};
VkExtent2D render{0, 0};
VkFormat window_format{VK_FORMAT_UNDEFINED};
// unorm (a storage image can't be srgb), the bits of the format of the window where it can be, which go to it by copies; another
// one goes by blits
VkFormat format{VK_FORMAT_UNDEFINED};
bool blit{false};
std::shared_ptr<image> backbuffer;
std::shared_ptr<image> hudless;
std::shared_ptr<image> depth;
std::shared_ptr<image> motion;
std::array<std::shared_ptr<image>, 3> outputs;
// the format of the window can't take the frames (said in the log once)
VkFormat unusable{VK_FORMAT_UNDEFINED};
};
// the ring of a frame some data of the draws goes through, the descriptor of which reads a window of it: the window starts again
// further on when it's full, and the pass set is bound again with the new offset
struct window_ring
@@ -382,6 +404,8 @@ struct scene_state
bool cluster_heatmap_tried{false};
// the targets of the viewports, [0] the main window
std::vector<viewport_targets> targets;
// the frame generation of dlss for the main window
generation_targets generation;
// the format of the levels of the bloom chains: no alpha, no negative values, half the memory of rgba16f, where the device
// draws to it
VkFormat bloom_format{VK_FORMAT_B10G11R11_UFLOAT_PACK32};
@@ -471,6 +495,14 @@ struct vulkan14_renderer::context
// 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};
// reflex (gfx.reflex, VK_NV_low_latency2): the timeline the sleep of the driver signals, its last value
VkSemaphore reflex{VK_NULL_HANDLE};
std::uint64_t reflexvalue{0};
// the presents of the main window with the frames generated by dlss (the render thread): when the last frame was handed over,
// the time between two (a moving average) which the frames of one are spread over, how long the last one took to be submitted
std::chrono::steady_clock::time_point present_last{};
double present_interval{0.0}; // seconds, 0: not known yet
double submit_time{0.0}; // seconds
// 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
@@ -503,6 +535,10 @@ struct vulkan14_renderer::context
std::vector<VkSemaphoreSubmitInfo> signals;
// the windows presented once the frame is submitted (the render thread)
std::vector<vk14::swapchain *> windows;
// the id of the frame for its present and for the markers of reflex
std::uint64_t present_id{0};
// when the main thread handed it over to the render thread
std::chrono::steady_clock::time_point handed{};
};
// the render thread and the job it's given
std::thread render_thread;
@@ -511,6 +547,7 @@ struct vulkan14_renderer::context
std::condition_variable render_idle;
render_job render_pending;
bool render_queued{false}; // render_pending holds a job not done yet
bool render_submitted{false}; // its frame is submitted, its windows may still be presented
bool render_stop{false};
char const *render_failure{nullptr};
// how long the main thread waited for the render thread at the start of the last frame, in milliseconds
@@ -527,8 +564,12 @@ struct vulkan14_renderer::context
// the job done; false: Failure says where it failed
bool execute(render_job &Job, char const *&Failure);
// the windows of the frame submitted last presented; false: the device or a surface is lost
bool present(std::vector<vk14::swapchain *> const &Windows);
// the part of it before the presents: its command buffers recorded and submitted
bool submit(render_job &Job, char const *&Failure);
// the windows of the frame submitted last presented, the frames generated for the main window before it (spread over the time
// of a frame when there's the render thread to wait); Id: the id of the present of the main window (0: none); false: the device or
// a surface is lost
bool present(std::vector<vk14::swapchain *> const &Windows, std::uint64_t Id);
// the render thread (gfx.vulkan.renderthread, M4): the frames are recorded, submitted and presented by it while the main
// thread goes on with the simulation of the next one. the main thread waits for it to be done before it begins a frame and
// before anything else touches the queue, the swapchains or the device as a whole
@@ -542,6 +583,9 @@ struct vulkan14_renderer::context
void hand_over(render_job &&Job);
// waits until the render thread is done with the jobs given to it; where it failed, if it did
char const *drain();
// waits until the render thread submitted the frame given to it (its presents may go on): the frames in flight and the streams
// are the main thread's again, the queue and the swapchains aren't until drain()
void wait_submitted();
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason);
// the value of the timeline the gpu reached, 0 when it can't be read

View File

@@ -325,6 +325,40 @@ bool device::create(instance const &Instance, std::string &Reason, std::vector<s
}
}
// reflex (gfx.reflex, and with the frame generation of dlss, which takes it for granted): the sleep of the driver before the
// simulation of a frame and the markers of the frame, with the ids of the presents the markers are for
VkPhysicalDevicePresentIdFeaturesKHR presentid{};
presentid.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
m_low_latency = false;
if ((Global.gfx_reflex > 0) || (Global.gfx_framegeneration > 1))
{
std::uint32_t count{0};
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, nullptr);
std::vector<VkExtensionProperties> available(count);
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, available.data());
auto const has = [&](char const *Name) { return std::any_of(available.begin(), available.end(), [Name](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, Name) == 0; }); };
VkPhysicalDeviceFeatures2 features{};
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features.pNext = &presentid;
vkGetPhysicalDeviceFeatures2(m_physical, &features);
presentid.pNext = nullptr;
m_low_latency = has(VK_NV_LOW_LATENCY_2_EXTENSION_NAME) && has(VK_KHR_PRESENT_ID_EXTENSION_NAME) && (presentid.presentId == VK_TRUE);
if (m_low_latency)
{
for (auto const *name : {VK_NV_LOW_LATENCY_2_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME})
{
if (std::none_of(extensions.begin(), extensions.end(), [name](char const *Entry) { return std::strcmp(Entry, name) == 0; }))
{
extensions.push_back(name);
}
}
}
else
{
log_info("gfx.reflex: " + m_capabilities.device_name + " has no VK_NV_low_latency2 with VK_KHR_present_id, reflex stays off");
}
}
// the ray traced reflections, asked for in eu07.ini (gfx.raytracing), where the device has what they need
VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration{};
acceleration.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
@@ -354,6 +388,12 @@ bool device::create(instance const &Instance, std::string &Reason, std::vector<s
VkDeviceCreateInfo create{};
create.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create.pNext = enabled.head();
if (m_low_latency)
{
presentid.presentId = VK_TRUE;
presentid.pNext = const_cast<void *>(create.pNext);
create.pNext = &presentid;
}
create.queueCreateInfoCount = static_cast<std::uint32_t>(queues.size());
create.pQueueCreateInfos = queues.data();
create.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size());
@@ -382,6 +422,10 @@ bool device::create(instance const &Instance, std::string &Reason, std::vector<s
{
log_info("ray queries on, for the ray traced reflections (gfx.raytracing)");
}
if (m_low_latency)
{
log_info("VK_NV_low_latency2 on, for reflex (gfx.reflex)");
}
return true;
}

View File

@@ -83,6 +83,11 @@ class device
{
return m_raytracing;
}
// VK_NV_low_latency2 with VK_KHR_present_id is on (reflex, gfx.reflex)
bool low_latency_enabled() const
{
return m_low_latency;
}
private:
static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits);
@@ -96,6 +101,7 @@ class device
bool m_portability_subset{false};
bool m_memory_budget{false};
bool m_raytracing{false};
bool m_low_latency{false};
VkQueue m_graphics_queue{VK_NULL_HANDLE};
VkQueue m_compute_queue{VK_NULL_HANDLE};

View File

@@ -19,10 +19,13 @@ http://mozilla.org/MPL/2.0/.
#if defined(WITH_DLSS)
#include <nvsdk_ngx_helpers.h>
#include <nvsdk_ngx_helpers_vk.h>
#include <nvsdk_ngx_helpers_dlssg_vk.h>
#endif
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <system_error>
@@ -196,16 +199,48 @@ struct dlss::state
feature seen_feature;
unsigned long long seen_memory{0};
// the frame generation: the most frames it makes between two, its own parameters (one map for each feature, as the guide of
// the frame generation asks), the feature the frames ask for and the one made
std::uint32_t generation_max{0};
NVSDK_NGX_Parameter *generation_parameters{nullptr};
struct generation_feature
{
glm::ivec2 output{0};
glm::ivec2 render{0};
VkFormat format{VK_FORMAT_UNDEFINED};
bool operator==(generation_feature const &Other) const
{
return (output == Other.output) && (render == Other.render) && (format == Other.format);
}
};
generation_feature generation_wanted;
generation_feature generation_made;
NVSDK_NGX_Handle *generation_handle{nullptr};
// what the replay of the frame evaluates, as the resources above
resource backbuffer, hudless, generation_depth, generation_motion;
std::vector<resource> generated;
NVSDK_NGX_DLSSG_Opt_Eval_Params generation_options{};
// set by the replay, read by the main thread once it waited for it
bool generation_failed{false};
std::string generation_error;
bool generation_error_told{false};
bool seen_generation_failed{false};
std::uint64_t generations{0};
std::uint64_t seen_generations{0};
// the debug window: the offsets of the jitter turned the other way, the history dropped
bool flip_x{false};
bool flip_y{false};
bool reset_wanted{false};
// the frame generation: the clip space of its matrices turned over (as the images it's given), its history dropped
bool generation_clip_flip{false};
bool generation_reset_wanted{false};
NVSDK_NGX_FeatureDiscoveryInfo discovery() const
NVSDK_NGX_FeatureDiscoveryInfo discovery(NVSDK_NGX_Feature const Feature) const
{
NVSDK_NGX_FeatureDiscoveryInfo info{};
info.SDKVersion = NVSDK_NGX_Version_API;
info.FeatureID = NVSDK_NGX_Feature_SuperSampling;
info.FeatureID = Feature;
info.Identifier.IdentifierType = NVSDK_NGX_Application_Identifier_Type_Project_Id;
info.Identifier.v.ProjectDesc.ProjectId = project_id;
info.Identifier.v.ProjectDesc.EngineType = NVSDK_NGX_ENGINE_TYPE_CUSTOM;
@@ -354,6 +389,138 @@ struct dlss::state
}
}
// a failed frame generation: the frames it was to make are the backbuffer, shown again
void copy_backbuffer(VkCommandBuffer const Buffer) const
{
if (false == backbuffer.valid)
{
return;
}
for (auto const &output : generated)
{
if (false == output.valid)
{
continue;
}
VkImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2;
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.extent = {std::min(backbuffer.width, output.width), std::min(backbuffer.height, output.height), 1};
VkCopyImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
copy.srcImage = backbuffer.image;
copy.srcImageLayout = VK_IMAGE_LAYOUT_GENERAL;
copy.dstImage = output.image;
copy.dstImageLayout = VK_IMAGE_LAYOUT_GENERAL;
copy.regionCount = 1;
copy.pRegions = &region;
vkCmdCopyImage2(Buffer, &copy);
}
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Buffer, &dependency);
}
void fail_generation(std::string const &Error)
{
if (generation_error.empty())
{
generation_error = Error;
}
generation_failed = true;
}
// the replay of the frame: the feature of the frame generation made when it isn't there, then evaluated once for each frame it
// makes, from the first one shown on
static void record_generation(VkCommandBuffer const Buffer, void *const Data)
{
auto &self{*static_cast<state *>(Data)};
if (self.generation_failed || (self.generation_parameters == nullptr) || self.generated.empty())
{
self.copy_backbuffer(Buffer);
return;
}
auto *parameters{self.generation_parameters};
if (self.generation_handle == nullptr)
{
NVSDK_NGX_DLSSG_Create_Params create{};
create.Width = static_cast<unsigned int>(self.generation_wanted.output.x);
create.Height = static_cast<unsigned int>(self.generation_wanted.output.y);
create.NativeBackbufferFormat = static_cast<unsigned int>(self.generation_wanted.format);
create.RenderWidth = static_cast<unsigned int>(self.generation_wanted.render.x);
create.RenderHeight = static_cast<unsigned int>(self.generation_wanted.render.y);
create.DynamicResolutionScaling = false;
// the helper of vulkan leaves out the size of the depth and the velocities, which the one of d3d12 gives
NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Width, create.Width);
NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Height, create.Height);
NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalWidth, create.RenderWidth);
NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalHeight, create.RenderHeight);
NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_DynamicResolution, 0);
auto const result{NGX_VK_CREATE_DLSSG(Buffer, 1, 1, &self.generation_handle, parameters, &create)};
if (NVSDK_NGX_FAILED(result) || (self.generation_handle == nullptr))
{
self.generation_handle = nullptr;
self.fail_generation("the feature of the frame generation of dlss can't be made: " + result_text(result));
self.copy_backbuffer(Buffer);
return;
}
self.generation_made = self.generation_wanted;
}
auto backbuffer{wrap(self.backbuffer, true)};
auto hudless{wrap(self.hudless, true)};
auto depth{wrap(self.generation_depth, true)};
auto motion{wrap(self.generation_motion, true)};
auto options{self.generation_options};
options.multiFrameCount = static_cast<unsigned int>(self.generated.size());
for (std::size_t i = 0; i < self.generated.size(); ++i)
{
auto output{wrap(self.generated[i], true)};
NVSDK_NGX_VK_DLSSG_Eval_Params evaluation{};
evaluation.pBackbuffer = &backbuffer;
evaluation.pDepth = &depth;
evaluation.pMVecs = &motion;
evaluation.pHudless = (self.hudless.valid ? &hudless : nullptr);
evaluation.pOutputInterpFrame = &output;
options.multiFrameIndex = static_cast<unsigned int>(i + 1);
auto const result{NGX_VK_EVALUATE_DLSSG(Buffer, self.generation_handle, parameters, &evaluation, &options)};
if (NVSDK_NGX_FAILED(result))
{
self.fail_generation("the frame generation of dlss can't be evaluated: " + result_text(result));
self.copy_backbuffer(Buffer);
return;
}
}
++self.generations;
}
void release_generation(deletion_queue *Deletions, std::uint64_t const Value)
{
if (generation_handle == nullptr)
{
return;
}
auto *old{generation_handle};
generation_handle = nullptr;
generation_made = generation_feature{};
if (Deletions != nullptr)
{
Deletions->push(Value, [old]() { NVSDK_NGX_VULKAN_ReleaseFeature(old); });
}
else
{
NVSDK_NGX_VULKAN_ReleaseFeature(old);
}
}
void release(deletion_queue *Deletions, std::uint64_t const Value)
{
if (handle == nullptr)
@@ -386,22 +553,50 @@ bool dlss::compiled()
return true;
}
namespace
{
// the features of ngx the renderer uses: the super resolution, and the frame generation where it's asked for
std::vector<NVSDK_NGX_Feature> wanted_features()
{
std::vector<NVSDK_NGX_Feature> features{NVSDK_NGX_Feature_SuperSampling};
if (Global.gfx_framegeneration > 1)
{
features.push_back(NVSDK_NGX_Feature_FrameGeneration);
}
return features;
}
void add_extensions(VkExtensionProperties const *Properties, std::uint32_t const Count, std::vector<std::string> &Extensions)
{
for (std::uint32_t i = 0; i < Count; ++i)
{
std::string const name{Properties[i].extensionName};
if (std::find(Extensions.begin(), Extensions.end(), name) == Extensions.end())
{
Extensions.push_back(name);
}
}
}
} // namespace
void dlss::instance_extensions(std::vector<std::string> &Extensions)
{
state temporary;
temporary.prepare_paths();
auto const info{temporary.discovery()};
std::uint32_t count{0};
VkExtensionProperties *properties{nullptr};
auto const result{NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(&info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
for (auto const feature : wanted_features())
{
log_warning("dlss: the instance extensions it needs can't be had from ngx: " + result_text(result));
return;
}
for (std::uint32_t i = 0; i < count; ++i)
{
Extensions.emplace_back(properties[i].extensionName);
auto const info{temporary.discovery(feature)};
std::uint32_t count{0};
VkExtensionProperties *properties{nullptr};
auto const result{NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(&info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
{
log_warning(std::string("dlss: the instance extensions ") + (feature == NVSDK_NGX_Feature_FrameGeneration ? "its frame generation" : "it") + " needs can't be had from ngx: " + result_text(result));
continue;
}
add_extensions(properties, count, Extensions);
}
}
@@ -409,18 +604,18 @@ void dlss::device_extensions(VkInstance const Instance, VkPhysicalDevice const P
{
state temporary;
temporary.prepare_paths();
auto const info{temporary.discovery()};
std::uint32_t count{0};
VkExtensionProperties *properties{nullptr};
auto const result{NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(Instance, Physical, &info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
for (auto const feature : wanted_features())
{
log_warning("dlss: the device extensions it needs can't be had from ngx: " + result_text(result));
return;
}
for (std::uint32_t i = 0; i < count; ++i)
{
Extensions.emplace_back(properties[i].extensionName);
auto const info{temporary.discovery(feature)};
std::uint32_t count{0};
VkExtensionProperties *properties{nullptr};
auto const result{NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(Instance, Physical, &info, &count, &properties)};
if (NVSDK_NGX_FAILED(result))
{
log_warning(std::string("dlss: the device extensions ") + (feature == NVSDK_NGX_Feature_FrameGeneration ? "its frame generation" : "it") + " needs can't be had from ngx: " + result_text(result));
continue;
}
add_extensions(properties, count, Extensions);
}
}
@@ -477,6 +672,47 @@ bool dlss::init(VkInstance const Instance, VkPhysicalDevice const Physical, VkDe
return false;
}
s.available = true;
// the frame generation, where it's asked for; without it dlss goes on alone
if (Global.gfx_framegeneration > 1)
{
std::string reason;
int fg{0};
int fgupdate{0};
unsigned int framesmax{0};
if (NVSDK_NGX_SUCCEED(s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_NeedsUpdatedDriver, &fgupdate)) && (fgupdate != 0))
{
unsigned int major{0}, minor{0};
s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMajor, &major);
s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMinor, &minor);
reason = "the graphics driver is too old for it, it needs " + std::to_string(major) + "." + std::to_string(minor) + " or newer";
}
else if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_Parameter_FrameGeneration_Available, &fg)) || (fg == 0))
{
reason = "it isn't available on this gpu (it needs a geforce rtx of the 40 series or newer) or with this driver";
}
else if (NVSDK_NGX_FAILED(NVSDK_NGX_VULKAN_AllocateParameters(&s.generation_parameters)) || (s.generation_parameters == nullptr))
{
s.generation_parameters = nullptr;
reason = "ngx gives no parameters for it";
}
else
{
// more than one frame between two (x3, x4) from the rtx 50 series on
if (NVSDK_NGX_FAILED(s.parameters->Get(NVSDK_NGX_DLSSG_Parameter_MultiFrameCountMax, &framesmax)) || (framesmax < 1))
{
framesmax = 1;
}
s.generation_max = std::min(framesmax, 3u);
}
if (s.generation_max == 0)
{
log_info("gfx.framegeneration: " + reason + ", the frames aren't generated");
}
else
{
log_info("dlss: the frame generation can make up to " + std::to_string(s.generation_max) + (s.generation_max == 1 ? " frame" : " frames") + " between two (x" + std::to_string(s.generation_max + 1) + ")");
}
}
return true;
}
@@ -487,8 +723,15 @@ void dlss::shutdown()
{
return;
}
// the gpu is idle: the feature goes at once
// the gpu is idle: the features go at once
s.release(nullptr, 0);
s.release_generation(nullptr, 0);
if (s.generation_parameters != nullptr)
{
NVSDK_NGX_VULKAN_DestroyParameters(s.generation_parameters);
}
s.generation_parameters = nullptr;
s.generation_max = 0;
if (s.parameters != nullptr)
{
NVSDK_NGX_VULKAN_DestroyParameters(s.parameters);
@@ -643,6 +886,125 @@ void dlss::evaluate(command_stream &Commands, frame_inputs const &Inputs, deleti
Commands.external(&state::record, &s);
}
std::uint32_t dlss::generation_max() const
{
auto const &s{*m_state};
return (s.available ? s.generation_max : 0);
}
bool dlss::generation_failed() const
{
return m_state->seen_generation_failed;
}
namespace
{
// a matrix of glm (columns of column vectors) as ngx takes it: rows, for row vectors multiplied by it from the left, which is the
// same memory
void ngx_matrix(glm::mat4 const &Matrix, float (&Target)[4][4])
{
std::memcpy(Target, &Matrix[0][0], sizeof(Target));
}
} // namespace
void dlss::generate(command_stream &Commands, generation_inputs const &Inputs, deletion_queue &Deletions, std::uint64_t const Value)
{
auto &s{*m_state};
if ((false == s.available) || (s.generation_max == 0) || (Inputs.backbuffer == nullptr) || (Inputs.depth == nullptr) || (Inputs.motion == nullptr) || Inputs.outputs.empty())
{
return;
}
state::generation_feature feature;
feature.output = {static_cast<int>(Inputs.backbuffer->desc().extent.width), static_cast<int>(Inputs.backbuffer->desc().extent.height)};
feature.render = {static_cast<int>(Inputs.depth->desc().extent.width), static_cast<int>(Inputs.depth->desc().extent.height)};
feature.format = Inputs.backbuffer->desc().format;
if ((s.generation_handle != nullptr) && (false == (feature == s.generation_made)))
{
s.release_generation(&Deletions, Value);
}
s.generation_wanted = feature;
auto const describe = [](image const *Image, state::resource &Resource) {
Resource.valid = (Image != nullptr);
if (Image == nullptr)
{
return;
}
Resource.image = Image->handle();
Resource.view = Image->view();
Resource.format = Image->desc().format;
Resource.aspect = Image->aspect();
Resource.width = Image->desc().extent.width;
Resource.height = Image->desc().extent.height;
};
describe(Inputs.backbuffer, s.backbuffer);
describe(Inputs.hudless, s.hudless);
describe(Inputs.depth, s.generation_depth);
describe(Inputs.motion, s.generation_motion);
s.generated.resize(std::min<std::size_t>(Inputs.outputs.size(), s.generation_max));
for (std::size_t i = 0; i < s.generated.size(); ++i)
{
describe(Inputs.outputs[i], s.generated[i]);
}
auto &options{s.generation_options};
options = NVSDK_NGX_DLSSG_Opt_Eval_Params{};
// the clip space of the renderer has its rows from the bottom of the picture up, as the images; the images the frame generation
// is given are turned over, and its clip space (that of d3d12, y up the picture) is the one of the renderer as it is. the debug
// window turns it over for the other way
glm::mat4 flip{1.f};
if (s.generation_clip_flip)
{
flip[1][1] = -1.f;
}
auto const viewtoclip{flip * Inputs.view_to_clip};
auto const cliptoprevious{flip * Inputs.clip_to_previous_clip * flip};
ngx_matrix(viewtoclip, options.cameraViewToClip);
ngx_matrix(glm::inverse(viewtoclip), options.clipToCameraView);
ngx_matrix(glm::mat4{1.f}, options.clipToLensClip);
ngx_matrix(cliptoprevious, options.clipToPrevClip);
ngx_matrix(glm::inverse(cliptoprevious), options.prevClipToClip);
// the offset as dlss takes it, along the rows turned over
auto const jitter{Inputs.jitter * glm::vec2{s.flip_x ? -1.f : 1.f, s.flip_y ? 1.f : -1.f}};
options.jitterOffset[0] = jitter.x;
options.jitterOffset[1] = jitter.y;
// the velocities are parts of the image along the rows of the renderer: pixels of the image turned over
options.mvecScale[0] = static_cast<float>(s.generation_depth.width);
options.mvecScale[1] = -static_cast<float>(s.generation_depth.height);
options.cameraPinholeOffset[0] = 0.f;
options.cameraPinholeOffset[1] = 0.f;
for (int i = 0; i < 3; ++i)
{
options.cameraPos[i] = Inputs.position[i];
options.cameraUp[i] = Inputs.up[i];
options.cameraRight[i] = Inputs.right[i];
options.cameraFwd[i] = Inputs.forward[i];
}
options.cameraNear = Inputs.znear;
options.cameraFar = Inputs.zfar;
options.cameraFOV = Inputs.fov;
options.cameraAspectRatio = Inputs.aspect;
options.colorBuffersHDR = false;
options.depthInverted = true;
options.cameraMotionIncluded = true;
options.reset = Inputs.reset || s.generation_reset_wanted;
s.generation_reset_wanted = false;
options.automodeOverrideReset = false;
options.notRenderingGameFrames = false;
options.orthoProjection = false;
// no value the velocities have marks them as missing
options.motionVectorsInvalidValue = FLT_MAX;
options.motionVectorsDilated = false;
options.menuDetectionEnabled = false;
options.mvecsSubrectSize = {s.generation_motion.width, s.generation_motion.height};
options.depthSubrectSize = {s.generation_depth.width, s.generation_depth.height};
options.hudLessSubrectSize = {s.hudless.width, s.hudless.height};
options.backbufferSubrectSize = {s.backbuffer.width, s.backbuffer.height};
options.outputInterpSubrectSize = {s.backbuffer.width, s.backbuffer.height};
Commands.external(&state::record_generation, &s);
}
std::string dlss::sync()
{
auto &s{*m_state};
@@ -650,12 +1012,19 @@ std::string dlss::sync()
s.seen_error = s.error;
s.seen_feature = ((s.handle != nullptr) ? s.made : s.wanted);
s.seen_memory = s.memory;
if ((false == s.failed) || s.error_told)
s.seen_generation_failed = s.generation_failed;
s.seen_generations = s.generations;
if (s.failed && (false == s.error_told))
{
return {};
s.error_told = true;
return s.error;
}
s.error_told = true;
return s.error;
if (s.generation_failed && (false == s.generation_error_told))
{
s.generation_error_told = true;
return s.generation_error;
}
return {};
}
bool dlss::failed() const
@@ -682,7 +1051,10 @@ std::string dlss::info() const
// the presets l and m take no exposure, they work it out themselves (the dlss programming guide, 3.9)
return std::string("dlss: ") + mode_name(feature.mode) + ", preset " + preset_name(feature.preset) + ", " + std::to_string(feature.render.x) + "x" + std::to_string(feature.render.y) + " to " + std::to_string(feature.output.x) + "x" +
std::to_string(feature.output.y) + ((feature.flags & NVSDK_NGX_DLSS_Feature_Flags_AutoExposure) != 0 ? ", its own exposure" : ", given the exposure of the tonemapping") + ", " +
std::to_string(s.seen_memory / (1024 * 1024)) + " MiB";
std::to_string(s.seen_memory / (1024 * 1024)) + " MiB" +
(s.generation_max == 0 ? std::string{}
: s.seen_generation_failed ? ", the frame generation off after an error"
: ", the frame generation up to x" + std::to_string(s.generation_max + 1) + " (" + std::to_string(s.seen_generations) + " frames generated from)");
}
void dlss::draw_debug_ui()
@@ -700,6 +1072,15 @@ void dlss::draw_debug_ui()
{
s.reset_wanted = true;
}
if (s.generation_max > 0)
{
ImGui::Checkbox("frame generation: the clip space of its matrices turned over", &s.generation_clip_flip);
ImGui::SameLine();
if (ImGui::Button("drop its history"))
{
s.generation_reset_wanted = true;
}
}
}
#else // without the sdk: dlss is never available
@@ -751,6 +1132,18 @@ std::uint32_t dlss::jitter_phases() const
void dlss::evaluate(command_stream &, frame_inputs const &, deletion_queue &, std::uint64_t) {}
std::uint32_t dlss::generation_max() const
{
return 0;
}
bool dlss::generation_failed() const
{
return false;
}
void dlss::generate(command_stream &, generation_inputs const &, deletion_queue &, std::uint64_t) {}
std::string dlss::sync()
{
return {};

View File

@@ -10,7 +10,11 @@ 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
// it dlss is never available and the renderer takes taa.
//
// the frame generation of dlss (gfx.framegeneration x2|x3|x4, with dlss or dlaa) makes one to three frames between the last frame
// and this one, from the images of both with the depth and the velocities; they are shown before this one, the presents spaced
// over the time of a frame (vulkan14_renderer::context::present())
#pragma once
@@ -48,6 +52,31 @@ class dlss
float frame_time{0.f}; // milliseconds
};
// what the frame generation is given, the images in the general layout: the backbuffer and the hudless the size and the format
// of the outputs (what the window shows, the rows from the top down), the depth and the velocities the size the scene is drawn
// at, turned over to the same way up (postfx_fg_flip.frag)
struct generation_inputs
{
image *backbuffer{nullptr}; // the frame as the window shows it, the user interface with it
image *hudless{nullptr}; // the same without the user interface
image *depth{nullptr}; // r32, reverse z
image *motion{nullptr}; // the velocities dlss takes, in parts of the image (uv) along the rows of the images of the renderer
std::vector<image *> outputs; // the frames generated, in the order they are shown
// the matrices without the offset of the frame, for the clip space of the image of the renderer (the rows from the bottom up)
glm::mat4 view_to_clip{1.f};
glm::mat4 clip_to_previous_clip{1.f};
glm::vec3 position{0.f}; // the camera in the world, and its axes
glm::vec3 up{0.f, 1.f, 0.f};
glm::vec3 right{1.f, 0.f, 0.f};
glm::vec3 forward{0.f, 0.f, -1.f};
float znear{0.1f};
float zfar{1000.f};
float fov{1.f}; // vertical, in radians
float aspect{1.f};
glm::vec2 jitter{0.f}; // as the frame_inputs, in pixels of the depth
bool reset{false};
};
dlss();
~dlss();
dlss(dlss const &) = delete;
@@ -79,6 +108,13 @@ class dlss
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 most frames the frame generation makes between two frames drawn (1 for x2, 3 for x4), 0 where it isn't available (not
// asked for at the start, an older gpu than the rtx 40 series, the driver)
std::uint32_t generation_max() const;
// the frames between the last frame and this one recorded to Commands (Inputs.outputs.size() of them); where the feature can't be
// made or evaluated the outputs get the backbuffer and the frame generation stays off from then on (generation_failed())
void generate(command_stream &Commands, generation_inputs const &Inputs, deletion_queue &Deletions, std::uint64_t Value);
bool generation_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

View File

@@ -529,16 +529,28 @@ bool vulkan14_renderer::Render()
Timer::subsystem.gfx_total.start();
// the render thread is done with the frame before: the queue, the swapchains and the frames are the main thread's again
if (auto const *failure{ctx.drain()})
// the render thread is done with the frame before: the queue, the swapchains and the frames are the main thread's again. with
// the frame generation it may still be presenting it, the queue and the swapchains are waited for before the windows are acquired
auto const latepresent{m_generated > 0};
if (latepresent)
{
ctx.wait_submitted();
}
else if (auto const *failure{ctx.drain()})
{
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
// reflex: the simulation of the frame is over, the frame is made
if (false == ctx.windows.empty())
{
ctx.windows.front()->latency_marker(ctx.framecount + 1, VK_LATENCY_MARKER_SIMULATION_END_NV);
}
// what the replay left to dlss; failed: off from now on, the scene of the main window stretched to it without anti-aliasing (the
// frame generation alone: the frames it was to make are the frame drawn, then it's off)
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");
vk14::log_error(error + (ctx.dlss.failed() ? "; dlss is off, the scene is stretched to the window without anti-aliasing" : "; the frame generation is off"));
}
// the gpu has to be done with the frame which used the slot before
@@ -618,26 +630,46 @@ bool vulkan14_renderer::Render()
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
switch (window->acquire(slot))
// the frame generation of dlss: the images of the main window for the frames it makes, where the scene is drawn with dlss. the
// frames it can make are known once the windows are acquired; till then the frame is made for all of them
auto const generating{(m_generated > 0) && simulation::is_ready && (false == Global.gfx_skiprendering) && dlss_active() && (false == ctx.dlss.generation_failed()) && (false == m_viewports.empty()) &&
(m_viewports.front().window == 0) && (m_viewports.front().output_width > 0) && (ctx.scene.generation.unusable == VK_FORMAT_UNDEFINED)};
m_generatedframe = (generating ? std::min(m_generated, ctx.windows.front()->generated_room()) : 0);
m_generationinputs = false;
auto const acquire_windows = [&]() {
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
if (window != ctx.windows.front())
switch (window->acquire(slot, ((window == ctx.windows.front()) && generating) ? m_generated : 0))
{
// a window of an extra viewport which can't be shown doesn't stop the others
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
if (window != ctx.windows.front())
{
// a window of an extra viewport which can't be shown doesn't stop the others
break;
}
ctx.acquired.clear();
report_device_lost("acquiring an image");
return false;
}
ctx.acquired.clear();
report_device_lost("acquiring an image");
return false;
}
m_generatedframe = 0;
if (generating && (false == ctx.acquired.empty()) && (ctx.acquired.front() == ctx.windows.front().get()) && (ctx.windows.front()->generated_count() > 0) && prepare_generation(0))
{
m_generatedframe = ctx.windows.front()->generated_count();
}
return true;
};
// with the frame generation the render thread may still be presenting the frame before, spaced over the time of a frame: the
// windows are acquired once the scene is recorded, after it's done
if ((false == latepresent) && (false == acquire_windows()))
{
return false;
}
frame.number = ctx.framecount;
@@ -792,6 +824,19 @@ bool vulkan14_renderer::Render()
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
if (latepresent)
{
if (auto const *failure{ctx.drain()})
{
report_device_lost(failure);
return false;
}
if (false == acquire_windows())
{
return false;
}
}
// the windows: the image of their viewport, the user interface over the main one
bool uidrawn{false};
for (auto *window : ctx.acquired)
@@ -806,6 +851,13 @@ bool vulkan14_renderer::Render()
}
}
auto const ismain{window == ctx.windows.front().get()};
if (ismain && (window->generated_count() > 0) && (viewport < m_viewports.size()))
{
// with the frames generated by dlss (or the frame drawn again in their images, where they can't be made)
render_generation(viewport);
uidrawn = true;
continue;
}
ctx.passes.begin_pass(ismain ? "ui" : "other viewports", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access)});
VkClearValue clear{};
clear.color = {{0.f, 0.f, 0.f, 1.f}};
@@ -892,7 +944,16 @@ bool vulkan14_renderer::Render()
signal.semaphore = window->present_semaphore();
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
job.signals.push_back(signal);
// the images of the frames generated, shown before it
for (std::uint32_t i = 0; i < window->generated_count(); ++i)
{
wait.semaphore = window->generated_acquire_semaphore(slot, i);
job.waits.push_back(wait);
signal.semaphore = window->generated_present_semaphore(i);
job.signals.push_back(signal);
}
}
job.present_id = ctx.framecount + 1;
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
@@ -1319,6 +1380,10 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
m_cabfuture = glm::mat4{1.f};
if (taa)
{
// the frame generation takes the camera without the offset of the frame
m_generationcamera.projection = m_renderpass.pass_camera.projection();
m_generationcamera.view = glm::mat3{glm::dmat3{m_renderpass.pass_camera.modelview()}};
m_generationcamera.position = glm::vec3{m_renderpass.pass_camera.position()};
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(
@@ -3086,6 +3151,311 @@ 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());
}
namespace
{
// the unorm format with the bits of a format of a window (a storage image can't be srgb)
VkFormat unorm_format(VkFormat const Format)
{
switch (Format)
{
case VK_FORMAT_B8G8R8A8_SRGB:
return VK_FORMAT_B8G8R8A8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
default:
return Format;
}
}
} // namespace
// the images of the frame generation for the size and the format of the main window; false: they can't be had
bool vulkan14_renderer::prepare_generation(std::size_t const Index)
{
auto &ctx{*m_context};
auto &generation{ctx.scene.generation};
auto const &window{*ctx.windows[m_viewports[Index].window]};
auto const extent{window.extent()};
auto const windowformat{window.format()};
if (generation.backbuffer && (generation.extent.width == extent.width) && (generation.extent.height == extent.height) && (generation.window_format == windowformat))
{
return true;
}
if (generation.unusable == windowformat)
{
return false;
}
auto const features = [&](VkFormat const Format) {
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(ctx.device.physical(), Format, &properties);
return properties.optimalTilingFeatures;
};
VkFormatFeatureFlags const needed{VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
// the bits of the format of the window, copied to it; or else rgba, blitted to it
auto format{unorm_format(windowformat)};
auto blit{false};
if ((features(format) & needed) != needed)
{
format = VK_FORMAT_R8G8B8A8_UNORM;
blit = true;
// a blit to an srgb window would encode the colour encoded already
if (((features(format) & (needed | VK_FORMAT_FEATURE_BLIT_SRC_BIT)) != (needed | VK_FORMAT_FEATURE_BLIT_SRC_BIT)) || ((features(windowformat) & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) || window.srgb())
{
generation.unusable = windowformat;
vk14::log_info("gfx.framegeneration: the format " + std::to_string(windowformat) + " of the main window can't be given the frames, they aren't generated");
return false;
}
}
vk14::generation_targets fresh;
fresh.extent = extent;
fresh.window_format = windowformat;
fresh.format = format;
fresh.blit = blit;
auto make = [&](std::shared_ptr<vk14::image> &Image, VkImageUsageFlags const Usage, char const *Name) {
vk14::image_desc desc;
desc.format = format;
desc.extent = {extent.width, extent.height, 1};
desc.usage = Usage | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
Image = std::make_shared<vk14::image>();
if (false == Image->create(ctx.memory, desc, Name))
{
Image.reset();
return false;
}
return true;
};
auto ok{make(fresh.backbuffer, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, "frame generation backbuffer") && make(fresh.hudless, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, "frame generation hudless")};
for (std::uint32_t i = 0; ok && (i < m_generated); ++i)
{
ok = make(fresh.outputs[i], VK_IMAGE_USAGE_TRANSFER_DST_BIT, "frame generated");
}
// what was made for the window before goes once the frames which use it are done
ctx.deletions.push(ctx.uploads.current_value(), [old = std::move(generation)]() {});
if (false == ok)
{
generation = vk14::generation_targets{};
generation.unusable = windowformat;
vk14::log_error("gfx.framegeneration: the images for the window of " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + " can't be made, the frames aren't generated");
return false;
}
generation = std::move(fresh);
return true;
}
// the depth and the velocities of the main view turned the way up of the window, for the frame generation (after dlss, which made
// the velocities of the sky)
void vulkan14_renderer::flip_generation_inputs(std::size_t const Index)
{
auto &ctx{*m_context};
auto &generation{ctx.scene.generation};
auto &targets{ctx.scene.targets[Index]};
if ((false == static_cast<bool>(targets.dlss_motion)) || (false == static_cast<bool>(targets.depth)))
{
return;
}
auto const render{targets.extent};
if ((false == static_cast<bool>(generation.depth)) || (generation.render.width != render.width) || (generation.render.height != render.height))
{
auto make = [&](std::shared_ptr<vk14::image> &Image, VkFormat const Format, char const *Name) {
vk14::image_desc desc;
desc.format = Format;
desc.extent = {render.width, render.height, 1};
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
Image = std::make_shared<vk14::image>();
if (false == Image->create(ctx.memory, desc, Name))
{
Image.reset();
return false;
}
return true;
};
ctx.deletions.push(ctx.uploads.current_value(), [depth = std::move(generation.depth), motion = std::move(generation.motion)]() {});
generation.depth.reset();
generation.motion.reset();
generation.render = {0, 0};
if ((false == make(generation.depth, VK_FORMAT_R32_SFLOAT, "frame generation depth")) || (false == make(generation.motion, VK_FORMAT_R16G16_SFLOAT, "frame generation velocity")))
{
generation.depth.reset();
generation.motion.reset();
return;
}
generation.render = render;
}
post_pass("postfx_fg_flip.frag", *generation.depth, {targets.depth.get()}, vk14::draw_constants{}, "frame generation inputs");
post_pass("postfx_fg_flip.frag", *generation.motion, {targets.dlss_motion.get()}, vk14::draw_constants{});
m_generationinputs = true;
}
// the main window with the frames generated by dlss: the frame without the user interface and with it, the frames generated from it
// and the last one, all of them copied to the images of the window acquired for them. without what the frame generation needs (its
// images, the inputs of the frame) every image of the window gets the frame drawn
void vulkan14_renderer::render_generation(std::size_t const Index)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &generation{scene.generation};
auto &window{*ctx.windows[m_viewports[Index].window]};
auto const count{std::min<std::uint32_t>(window.generated_count(), m_generated)};
auto const extent{window.extent()};
auto const drawn{(Index < scene.targets.size()) && scene.targets[Index].output};
VkClearValue clear{};
clear.color = {{0.f, 0.f, 0.f, 1.f}};
auto const ready{drawn && m_generationinputs && generation.backbuffer && generation.depth && (generation.extent.width == extent.width) && (generation.extent.height == extent.height)};
if (false == ready)
{
for (std::uint32_t i = 0; i <= window.generated_count(); ++i)
{
auto const last{i == window.generated_count()};
ctx.passes.begin_pass(last ? "ui" : "frame generation: the frame again",
{last ? window.use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access) : window.generated_use(i, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access)});
auto attachment{attachment_info(last ? window.view() : window.generated_view(i), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, clear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
ctx.commands->begin_rendering(&rendering);
if (drawn)
{
present_viewport(Index);
}
// the user interface in each of them (kept for the next one but the last)
ctx.imgui.record(ctx.commands, extent, window.format(), window.srgb(), false == last);
ctx.commands->end_rendering();
ctx.passes.end_pass();
}
std::vector<vk14::image_use> presents{window.use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)};
for (std::uint32_t i = 0; i < window.generated_count(); ++i)
{
presents.push_back(window.generated_use(i, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE));
}
vk14::barriers(ctx.commands, presents);
return;
}
// the frame without the user interface, and with it
auto drawto = [&](vk14::image &Target, bool const Ui, char const *Pass) {
ctx.passes.begin_pass(Pass, {vk14::use_of(Target, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access)});
auto attachment{attachment_info(Target.view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, clear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
ctx.commands->begin_rendering(&rendering);
present_viewport(Index, &Target);
if (Ui)
{
ctx.imgui.record(ctx.commands, extent, Target.desc().format, false);
}
ctx.commands->end_rendering();
ctx.passes.end_pass();
};
drawto(*generation.hudless, false, "frame generation hudless");
drawto(*generation.backbuffer, true, "ui");
// the frames between the last one and this one, by the passes of the library, whichever stages they are
auto const stage{VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
auto const read{VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT};
std::vector<vk14::image_use> uses{vk14::use_of(*generation.backbuffer, VK_IMAGE_LAYOUT_GENERAL, stage, read), vk14::use_of(*generation.hudless, VK_IMAGE_LAYOUT_GENERAL, stage, read),
vk14::use_of(*generation.depth, VK_IMAGE_LAYOUT_GENERAL, stage, read), vk14::use_of(*generation.motion, VK_IMAGE_LAYOUT_GENERAL, stage, read)};
vk14::dlss::generation_inputs inputs;
inputs.backbuffer = generation.backbuffer.get();
inputs.hudless = generation.hudless.get();
inputs.depth = generation.depth.get();
inputs.motion = generation.motion.get();
for (std::uint32_t i = 0; i < count; ++i)
{
uses.push_back(vk14::use_of(*generation.outputs[i], VK_IMAGE_LAYOUT_GENERAL, stage, read | VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT));
inputs.outputs.push_back(generation.outputs[i].get());
}
ctx.passes.begin_pass("frame generation", uses);
auto const &camera{m_generationcamera};
inputs.view_to_clip = camera.projection;
inputs.clip_to_previous_clip = m_taa.reprojection_without_offset();
inputs.position = camera.position;
// the axes of the view in the world: the rows of the rotation of the world to the view (the view looks along its -z)
inputs.right = glm::vec3{camera.view[0][0], camera.view[1][0], camera.view[2][0]};
inputs.up = glm::vec3{camera.view[0][1], camera.view[1][1], camera.view[2][1]};
inputs.forward = -glm::vec3{camera.view[0][2], camera.view[1][2], camera.view[2][2]};
inputs.znear = m_colorpass.znear;
inputs.zfar = m_colorpass.zfar;
auto const yscale{camera.projection[1][1]};
inputs.fov = (yscale > 0.f ? 2.f * std::atan(1.f / yscale) : glm::radians(45.f));
inputs.aspect = ((camera.projection[0][0] > 0.f) ? yscale / camera.projection[0][0] : 1.f);
inputs.jitter = m_taa.jitter();
// its history is of no use after a frame without it (minimized, not drawn, without the images)
inputs.reset = (false == m_taa.history_valid()) || (m_generationframe + 1 != ctx.framecount);
m_generationframe = ctx.framecount;
ctx.dlss.generate(*ctx.commands, inputs, ctx.deletions, ctx.uploads.current_value());
ctx.passes.end_pass();
// the frames to the images of the window: the ones generated first, in the order they are shown, then the one drawn
auto const transfer{VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT};
std::vector<vk14::image_use> copies{vk14::use_of(*generation.backbuffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, transfer, VK_ACCESS_2_TRANSFER_READ_BIT),
window.use(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, transfer, VK_ACCESS_2_TRANSFER_WRITE_BIT)};
for (std::uint32_t i = 0; i < window.generated_count(); ++i)
{
auto &source{*generation.outputs[std::min(i, count - 1)]};
copies.push_back(vk14::use_of(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, transfer, VK_ACCESS_2_TRANSFER_READ_BIT));
copies.push_back(window.generated_use(i, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, transfer, VK_ACCESS_2_TRANSFER_WRITE_BIT));
}
ctx.passes.begin_pass("frame generation to the window", copies);
auto const send = [&](vk14::image &Source, VkImage const Destination) {
if (generation.blit)
{
VkImageBlit2 region{};
region.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.srcOffsets[1] = {static_cast<std::int32_t>(extent.width), static_cast<std::int32_t>(extent.height), 1};
region.dstOffsets[1] = region.srcOffsets[1];
VkBlitImageInfo2 blit{};
blit.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
blit.srcImage = Source.handle();
blit.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
blit.dstImage = Destination;
blit.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
blit.regionCount = 1;
blit.pRegions = &region;
blit.filter = VK_FILTER_NEAREST;
ctx.commands->blit_image2(&blit);
return;
}
VkImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2;
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.extent = {extent.width, extent.height, 1};
VkCopyImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
copy.srcImage = Source.handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstImage = Destination;
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = 1;
copy.pRegions = &region;
ctx.commands->copy_image2(&copy);
};
for (std::uint32_t i = 0; i < window.generated_count(); ++i)
{
send(*generation.outputs[std::min(i, count - 1)], window.generated_image(i));
}
send(*generation.backbuffer, window.image());
ctx.passes.end_pass();
std::vector<vk14::image_use> presents{window.use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)};
for (std::uint32_t i = 0; i < window.generated_count(); ++i)
{
presents.push_back(window.generated_use(i, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE));
}
vk14::barriers(ctx.commands, presents);
}
// 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)
@@ -3109,6 +3479,10 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
{
render_dlss(Index);
hdr = targets.dlss_output.get();
if ((m_generatedframe > 0) && m_viewports[Index].main)
{
flip_generation_inputs(Index);
}
}
// 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)
@@ -3135,6 +3509,12 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
{
vk14::draw_constants constants;
constants.data[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter);
// with the frames generated by dlss each frame drawn is shown for a part of its time: the blur as much shorter (the guide of
// the frame generation)
if (m_generationinputs && m_viewports[Index].main)
{
constants.data[0].x /= static_cast<float>(1 + m_generatedframe);
}
post_pass("postfx_motionblur.frag", *targets.blurred, {hdr, targets.motion_image.get(), targets.depth.get()}, constants, "resolve, ambient occlusion, motion blur");
hdr = targets.blurred.get();
}
@@ -3172,21 +3552,25 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
vk14::barriers(commands, {vk14::use_of(*targets.output, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, sampled_access)});
}
// the output of a viewport drawn to its window turned upside down (decision K8), inside the rendering of the window
void vulkan14_renderer::present_viewport(std::size_t const Index)
// the output of a viewport drawn to its window turned upside down (decision K8), inside the rendering of the window; or to Target,
// an image of the size of the window in the place of it (the frame generation, unorm)
void vulkan14_renderer::present_viewport(std::size_t const Index, vk14::image const *Target)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto const &window{*ctx.windows[m_viewports[Index].window]};
auto const commands{ctx.commands};
auto const format{Target != nullptr ? Target->desc().format : window.format()};
auto const srgb{Target != nullptr ? false : window.srgb()};
VkExtent2D const extent{Target != nullptr ? VkExtent2D{Target->desc().extent.width, Target->desc().extent.height} : window.extent()};
// the output encoded already goes as it is; an srgb output to a window which isn't (no srgb format of the surface) is encoded
auto const pipeline{post_pipeline("present.frag", static_cast<int>(window.format()), (targets.srgb && (false == window.srgb())) ? 1 : 0)};
auto const pipeline{post_pipeline("present.frag", static_cast<int>(format), (targets.srgb && (false == srgb)) ? 1 : 0)};
if (pipeline == nullptr)
{
return;
}
vk14::set_draw_state(commands, window.extent());
vk14::set_draw_state(commands, extent);
commands->bind_pipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, reinterpret_cast<VkPipeline>(pipeline));
commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
std::array<vk14::descriptor, vk14::bindings::POST_TEXTURES + 1> descriptors;

View File

@@ -217,10 +217,13 @@ void imgui_backend::Release_Image(std::uint64_t const Image)
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
}
void imgui_backend::record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
void imgui_backend::record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb, bool const Keep)
{
auto const *drawdata{m_drawdata};
m_drawdata = nullptr;
if (false == Keep)
{
m_drawdata = nullptr;
}
if ((drawdata == nullptr) || (drawdata->CmdListsCount == 0) || (drawdata->TotalVtxCount == 0) || (m_resources == nullptr))
{
return;

View File

@@ -50,7 +50,8 @@ class imgui_backend : public imgui_renderer
}
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
// image encodes the colour itself
void record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
// Keep: the frame of imgui stays for another record (the same user interface in more than one image)
void record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb, bool Keep = false);
// the frame of imgui is dropped (nothing to draw it to)
void skip()
{

View File

@@ -40,6 +40,36 @@ float srgb_to_linear(float const Value)
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
}
// waits until Time, to a part of a millisecond: the presents of the frames generated by dlss are spread over the time of a frame. the
// sleeps of windows go by its timer, 15.6 ms unless a program asks for less, so it's a timer of a high resolution there (windows 10
// 1803 and newer), with what's left of the time (or all of it without that timer) waited out by yielding
void wait_until(std::chrono::steady_clock::time_point const Time)
{
#if defined(_WIN32)
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
#endif
static thread_local HANDLE const timer{::CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS)};
auto const left{std::chrono::duration_cast<std::chrono::nanoseconds>(Time - std::chrono::steady_clock::now()).count()};
if ((timer != nullptr) && (left > 200000))
{
// in units of 100 ns, negative for a time from now; a little early, for the yields to end it on time
LARGE_INTEGER due;
due.QuadPart = -static_cast<LONGLONG>((left - 100000) / 100);
if (::SetWaitableTimerEx(timer, &due, 0, nullptr, nullptr, nullptr, 0))
{
::WaitForSingleObject(timer, INFINITE);
}
}
while (std::chrono::steady_clock::now() < Time)
{
std::this_thread::yield();
}
#else
std::this_thread::sleep_until(Time);
#endif
}
// the threads of the renderer (gfx.threads), the calling one with them: 0, the cores less two
std::size_t render_threads()
{
@@ -70,6 +100,18 @@ bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::s
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
// the timeline the sleep of reflex signals
if (device.low_latency_enabled())
{
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &reflex)))
{
reflex = VK_NULL_HANDLE;
}
else
{
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(reflex), "reflex sleep");
}
}
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
@@ -168,8 +210,29 @@ bool vulkan14_renderer::context::record(frame &Frame, bool const Uploads)
}
bool vulkan14_renderer::context::execute(render_job &Job, char const *&Failure)
{
if (false == submit(Job, Failure))
{
return false;
}
if ((false == Job.windows.empty()) && (false == present(Job.windows, Job.present_id)))
{
Failure = "presenting";
return false;
}
return true;
}
bool vulkan14_renderer::context::submit(render_job &Job, char const *&Failure)
{
auto &frame{*Job.target};
// reflex: the recording and the submit are of the frame with the id of its present
auto const *main{windows.empty() ? nullptr : windows.front().get()};
auto const lowlatency{(main != nullptr) && main->low_latency() && (Job.present_id > 0)};
if (lowlatency)
{
main->latency_marker(Job.present_id, VK_LATENCY_MARKER_RENDERSUBMIT_START_NV);
}
if (false == record(frame, Job.uploaded))
{
Failure = "recording a frame";
@@ -194,6 +257,13 @@ bool vulkan14_renderer::context::execute(render_job &Job, char const *&Failure)
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
VkLatencySubmissionPresentIdNV latency{};
latency.sType = VK_STRUCTURE_TYPE_LATENCY_SUBMISSION_PRESENT_ID_NV;
latency.presentID = Job.present_id;
if (lowlatency)
{
submit.pNext = &latency;
}
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(Job.waits.size());
submit.pWaitSemaphoreInfos = Job.waits.data();
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
@@ -205,35 +275,101 @@ bool vulkan14_renderer::context::execute(render_job &Job, char const *&Failure)
Failure = "submitting a frame";
return false;
}
if ((false == Job.windows.empty()) && (false == present(Job.windows)))
if (lowlatency)
{
Failure = "presenting";
return false;
main->latency_marker(Job.present_id, VK_LATENCY_MARKER_RENDERSUBMIT_END_NV);
}
return true;
}
bool vulkan14_renderer::context::present(std::vector<vk14::swapchain *> const &Windows)
bool vulkan14_renderer::context::present(std::vector<vk14::swapchain *> const &Windows, std::uint64_t const Id)
{
auto *main{windows.empty() ? nullptr : windows.front().get()};
auto const withmain{(main != nullptr) && (std::find(Windows.begin(), Windows.end(), main) != Windows.end())};
auto const generated{withmain ? main->generated_count() : 0u};
auto const lowlatency{withmain && main->low_latency() && (Id > 0)};
auto const ids{device.low_latency_enabled()};
// the frames generated by dlss go first, one after another, then the frame drawn: at equal times over the time of a frame, as
// measured between the frames the main thread handed over (the guide of the frame generation, 7), and done in time for the
// next one to be submitted. the main thread meanwhile goes on with the next frame up to its windows; without the render thread
// they go at once (vsync spaces them)
auto const start{std::chrono::steady_clock::now()};
auto const paced{(generated > 0) && render_threaded() && (present_interval > 0.0)};
auto spacing{0.0};
if (paced)
{
spacing = std::min(present_interval / static_cast<double>(generated + 1), std::max(0.0, 0.95 * present_interval - submit_time) / static_cast<double>(generated));
}
auto const step{std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(spacing))};
for (std::uint32_t i = 0; i < generated; ++i)
{
if (paced && (i > 0))
{
wait_until(start + step * i);
}
auto const semaphore{main->generated_present_semaphore(i)};
auto const swapchain{main->handle()};
auto const image{main->generated_index(i)};
std::uint64_t const noid{0};
VkPresentIdKHR presentid{};
presentid.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
presentid.swapchainCount = 1;
presentid.pPresentIds = &noid;
VkPresentInfoKHR info{};
info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
info.pNext = (ids ? &presentid : nullptr);
info.waitSemaphoreCount = 1;
info.pWaitSemaphores = &semaphore;
info.swapchainCount = 1;
info.pSwapchains = &swapchain;
info.pImageIndices = &image;
auto const result{vkQueuePresentKHR(device.graphics_queue(), &info)};
main->presented(result);
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
return false;
}
}
if (paced)
{
wait_until(start + step * generated);
}
std::vector<VkSemaphore> waits;
std::vector<VkSwapchainKHR> swapchains;
std::vector<std::uint32_t> images;
std::vector<std::uint64_t> presentids;
std::vector<VkResult> results(Windows.size(), VK_SUCCESS);
for (auto *window : Windows)
{
waits.push_back(window->present_semaphore());
swapchains.push_back(window->handle());
images.push_back(window->image_index());
presentids.push_back(window == main ? Id : 0);
}
VkPresentIdKHR presentid{};
presentid.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
presentid.swapchainCount = static_cast<std::uint32_t>(presentids.size());
presentid.pPresentIds = presentids.data();
VkPresentInfoKHR info{};
info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
info.pNext = (ids ? &presentid : nullptr);
info.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
info.pWaitSemaphores = waits.data();
info.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
info.pSwapchains = swapchains.data();
info.pImageIndices = images.data();
info.pResults = results.data();
if (lowlatency)
{
main->latency_marker(Id, VK_LATENCY_MARKER_PRESENT_START_NV);
}
auto const result{vkQueuePresentKHR(device.graphics_queue(), &info)};
if (lowlatency)
{
main->latency_marker(Id, VK_LATENCY_MARKER_PRESENT_END_NV);
}
for (std::size_t i = 0; i < Windows.size(); ++i)
{
Windows[i]->presented(results[i]);
@@ -264,7 +400,28 @@ void vulkan14_renderer::context::start_render_thread()
auto done{false};
try
{
done = execute(render_pending, failure);
// the frame submitted, the main thread may go on with the next one (wait_submitted()) while the windows are
// presented: with the frames generated by dlss the presents are spread over the time of a frame
auto const started{std::chrono::steady_clock::now()};
done = submit(render_pending, failure);
submit_time = std::chrono::duration<double>(std::chrono::steady_clock::now() - started).count();
auto const handed{render_pending.handed};
if (present_last != std::chrono::steady_clock::time_point{})
{
auto const period{std::chrono::duration<double>(handed - present_last).count()};
present_interval = ((period > 0.0) && (period < 0.25)) ? (present_interval > 0.0 ? 0.9 * present_interval + 0.1 * period : period) : 0.0;
}
present_last = handed;
{
std::lock_guard<std::mutex> submitted(render_mutex);
render_submitted = true;
}
render_idle.notify_all();
if (done && (false == render_pending.windows.empty()) && (false == present(render_pending.windows, render_pending.present_id)))
{
done = false;
failure = "presenting";
}
}
catch (std::exception const &Error)
{
@@ -307,11 +464,25 @@ void vulkan14_renderer::context::hand_over(render_job &&Job)
std::unique_lock<std::mutex> lock(render_mutex);
render_idle.wait(lock, [this]() { return false == render_queued; });
render_pending = std::move(Job);
render_pending.handed = std::chrono::steady_clock::now();
render_queued = true;
render_submitted = false;
}
render_wake.notify_one();
}
void vulkan14_renderer::context::wait_submitted()
{
if (false == render_threaded())
{
return;
}
auto const start{std::chrono::steady_clock::now()};
std::unique_lock<std::mutex> lock(render_mutex);
render_idle.wait(lock, [this]() { return (false == render_queued) || render_submitted; });
render_wait = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
}
char const *vulkan14_renderer::context::drain()
{
if (false == render_threaded())
@@ -431,10 +602,15 @@ void vulkan14_renderer::context::destroy()
{
vkDestroySemaphore(handle, timeline, nullptr);
}
if (reflex != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, reflex, nullptr);
}
}
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
reflex = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
}
@@ -518,6 +694,20 @@ void vulkan14_renderer::start_dlss()
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");
// the frame generation, as far as the gpu goes (x3 and x4 from the rtx 50 series on)
if (Global.gfx_framegeneration > 1)
{
auto const wanted{static_cast<std::uint32_t>(Global.gfx_framegeneration - 1)};
m_generated = std::min(wanted, ctx.dlss.generation_max());
if ((m_generated > 0) && (m_generated < wanted))
{
vk14::log_info("gfx.framegeneration x" + std::to_string(wanted + 1) + ": the gpu goes up to x" + std::to_string(m_generated + 1) + ", using that");
}
if ((m_generated > 0) && (false == Global.gfx_vulkan_renderthread))
{
vk14::log_info("gfx.framegeneration: without the render thread (gfx.vulkan.renderthread no) the frames generated are shown at once, spaced only by vsync");
}
}
return;
}
reason = "it doesn't upscale to the window of " + std::to_string(output.x) + "x" + std::to_string(output.y);
@@ -547,6 +737,10 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
{
vk14::dlss::instance_extensions(config.wanted_extensions);
}
else if (Global.gfx_framegeneration > 1)
{
vk14::log_info("gfx.framegeneration: it goes with gfx.antialiasing dlss or dlaa, the frames aren't generated");
}
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)};
@@ -564,7 +758,15 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
if (ok)
{
auto mainwindow{std::make_unique<vk14::swapchain>()};
// the frame generation needs an image of the swapchain for each frame it makes, and reflex with it (the guide of the frame
// generation; the latency the frames shown later add is taken back by it)
mainwindow->set_generated(m_generated);
mainwindow->set_low_latency(std::max(Global.gfx_reflex, m_generated > 0 ? 1 : 0));
ok = mainwindow->create(ctx.instance, ctx.device, Window, "main window", framesinflight, reason);
if (ok && (Global.gfx_reflex > 0) && ctx.device.low_latency_enabled() && (false == mainwindow->low_latency()) && (mainwindow->handle() != VK_NULL_HANDLE))
{
vk14::log_info("gfx.reflex: the driver didn't turn it on for the main window");
}
ctx.windows.emplace_back(std::move(mainwindow));
}
if (false == ok)
@@ -705,7 +907,7 @@ void vulkan14_renderer::SwapBuffers()
// with the render thread the windows are presented by it (ctx.acquired was handed over to it)
if (false == ctx.acquired.empty())
{
if (false == ctx.present(ctx.acquired))
if (false == ctx.present(ctx.acquired, ctx.framecount + 1))
{
report_device_lost("presenting");
}
@@ -716,6 +918,34 @@ void vulkan14_renderer::SwapBuffers()
Timer::subsystem.gfx_swap.stop();
}
void vulkan14_renderer::Frame_Start()
{
if (!m_context || m_context->devicelost || m_context->windows.empty() || (m_context->reflex == VK_NULL_HANDLE))
{
return;
}
auto &ctx{*m_context};
auto const &main{*ctx.windows.front()};
if (false == main.low_latency())
{
return;
}
// the driver says how long to wait for the gpu before the input and the simulation of the frame, once between two presents. the
// semaphore is waited for at most a while: a window without a swapchain (minimized) has nothing for the driver to measure
if (main.latency_sleep(ctx.reflex, ctx.reflexvalue + 1))
{
++ctx.reflexvalue;
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &ctx.reflex;
wait.pValues = &ctx.reflexvalue;
vkWaitSemaphores(ctx.device.handle(), &wait, 100000000ull);
}
main.latency_marker(ctx.framecount + 1, VK_LATENCY_MARKER_SIMULATION_START_NV);
main.latency_marker(ctx.framecount + 1, VK_LATENCY_MARKER_INPUT_SAMPLE_NV);
}
float vulkan14_renderer::Framerate()
{
return m_framerate;

View File

@@ -109,6 +109,8 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
glm::dvec3 Camera_Position() const override;
// maintenance
void Update(double const Deltatime) override;
// reflex (gfx.reflex): the sleep of the driver before the simulation of the frame
void Frame_Start() override;
void Update_Pick_Control() override;
void Update_Pick_Node() override;
glm::dvec3 Update_Mouse_Position() override;
@@ -302,8 +304,14 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// 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 output of a viewport to its window (inside its rendering), or to Target (the images of the frame generation)
void present_viewport(std::size_t const Viewport, vk14::image const *Target = nullptr);
// the frame generation of dlss for the main window (gfx.framegeneration): its images ready for the size and the format of the
// window, the depth and the velocities of the scene turned over for it; the frames generated, then the frames of the window
// copied to its images
bool prepare_generation(std::size_t const Viewport);
void flip_generation_inputs(std::size_t const Viewport);
void render_generation(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)
void update_lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
void write_local_light(gl::light_element &Light, gfx::local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
@@ -439,6 +447,18 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
glm::ivec2 m_dlssrender{0};
glm::ivec2 m_dlssoutput{0};
bool m_dlssreset{true};
// the frame generation of dlss: the frames generated for each frame drawn as set at the start (0: off), and the frames of the
// frame being made (fewer, or none, where the window has no room for them or isn't ready); the matrices of the main view it takes
std::uint32_t m_generated{0};
std::uint32_t m_generatedframe{0};
bool m_generationinputs{false}; // the depth and the velocities of the frame are turned over for it
std::uint64_t m_generationframe{0}; // the last frame it made frames for
struct generation_camera
{
glm::mat4 projection{1.f};
glm::vec3 position{0.f};
glm::mat3 view{1.f};
} m_generationcamera;
void start_dlss();
// dlss is used for the main viewport this frame
bool dlss_active() const;

View File

@@ -632,7 +632,7 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
}
if (Global.gfx_dlss())
{
for (auto const *name : {"postfx_dlss_motion.frag", "postfx_dlss_exposure.frag"})
for (auto const *name : {"postfx_dlss_motion.frag", "postfx_dlss_exposure.frag", "postfx_fg_flip.frag"})
{
scene.post_fragments.emplace(name, ctx.shaders.get(name));
}
@@ -831,6 +831,7 @@ void vulkan14_renderer::destroy_scene()
// the pipelines belong to the pipeline library
scene.materials.clear();
scene.targets.clear();
scene.generation = vk14::generation_targets{};
scene.white.reset();
scene.shadow_empty.reset();
scene.shadow_map.reset();

View File

@@ -94,8 +94,8 @@ bool swapchain::create(instance const &Instance, device const &Device, GLFWwindo
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
m_acquiresemaphores.assign(Frames, VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < Frames; ++i)
m_acquiresemaphores.assign(Frames * (1 + m_generatedcapacity), VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < m_acquiresemaphores.size(); ++i)
{
if (!VK14_VERIFY(vkCreateSemaphore(Device.handle(), &semaphoreinfo, nullptr, &m_acquiresemaphores[i])))
{
@@ -210,7 +210,8 @@ bool swapchain::build(std::string &Reason)
}
}
auto imagecount{capabilities.minImageCount + 1};
// with the generated frames one image more for each of them: all the images of a frame are acquired at once
auto imagecount{capabilities.minImageCount + 1 + m_generatedcapacity};
if (capabilities.maxImageCount > 0)
{
imagecount = std::min(imagecount, capabilities.maxImageCount);
@@ -253,6 +254,15 @@ bool swapchain::build(std::string &Reason)
create.presentMode = presentmode;
create.clipped = VK_TRUE;
create.oldSwapchain = oldswapchain;
// reflex (VK_NV_low_latency2) is told about the swapchain when it's made
VkSwapchainLatencyCreateInfoNV latency{};
latency.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_LATENCY_CREATE_INFO_NV;
latency.latencyModeEnable = VK_TRUE;
auto const lowlatency{(m_lowlatency > 0) && m_device->low_latency_enabled()};
if (lowlatency)
{
create.pNext = &latency;
}
VkSwapchainKHR swapchain{VK_NULL_HANDLE};
auto const created{vkCreateSwapchainKHR(device, &create, nullptr, &swapchain)};
@@ -267,6 +277,21 @@ bool swapchain::build(std::string &Reason)
return false;
}
m_swapchain = swapchain;
m_lowlatencyon = false;
if (lowlatency && (vkSetLatencySleepModeNV != nullptr))
{
VkLatencySleepModeInfoNV mode{};
mode.sType = VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV;
mode.lowLatencyMode = VK_TRUE;
mode.lowLatencyBoost = (m_lowlatency > 1 ? VK_TRUE : VK_FALSE);
mode.minimumIntervalUs = 0;
auto const result{vkSetLatencySleepModeNV(device, m_swapchain, &mode)};
m_lowlatencyon = (result == VK_SUCCESS);
if (false == m_lowlatencyon)
{
log_warning(m_name + ": reflex can't be turned on (" + result_string(result) + ")");
}
}
m_format = chosen.format;
m_colorspace = chosen.colorSpace;
m_presentmode = presentmode;
@@ -281,6 +306,8 @@ bool swapchain::build(std::string &Reason)
vkGetSwapchainImagesKHR(device, m_swapchain, &count, nullptr);
m_images.resize(count);
vkGetSwapchainImagesKHR(device, m_swapchain, &count, m_images.data());
// the images the program may hold acquired at once (an acquire which waits without end may be done only within them)
m_acquirable = (count > capabilities.minImageCount ? count - capabilities.minImageCount : 1);
m_states.assign(count, image_state{});
VkSemaphoreCreateInfo semaphoreinfo{};
@@ -309,8 +336,9 @@ bool swapchain::build(std::string &Reason)
return true;
}
swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame, std::uint32_t const Generated)
{
m_generated.clear();
if (outdated())
{
std::string reason;
@@ -325,7 +353,7 @@ swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
return acquire_result::skipped;
}
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, m_acquiresemaphores[Frame], VK_NULL_HANDLE, &m_image)};
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, acquire_semaphore(Frame), VK_NULL_HANDLE, &m_image)};
if ((result == VK_SUCCESS) || (result == VK_SUBOPTIMAL_KHR))
{
// what the image held is of no use; its use waits for the acquire semaphore, which the submit waits for at the colour output
@@ -335,6 +363,28 @@ swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
}
m_states[m_image] = image_state{VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE};
}
// the images of the generated frames, shown before the one drawn; the swapchain has one image more for each of them, so the
// images of a frame can all be acquired. one which can't be had leaves fewer of them
if ((result == VK_SUCCESS) || (result == VK_SUBOPTIMAL_KHR))
{
auto const room{std::min(std::min(Generated, m_generatedcapacity), m_acquirable - 1)};
for (std::uint32_t i = 0; i < room; ++i)
{
std::uint32_t image{0};
auto const extra{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, generated_acquire_semaphore(Frame, i), VK_NULL_HANDLE, &image)};
if ((extra != VK_SUCCESS) && (extra != VK_SUBOPTIMAL_KHR))
{
m_rebuild = true;
break;
}
if (extra == VK_SUBOPTIMAL_KHR)
{
m_rebuild = true;
}
m_states[image] = image_state{VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE};
m_generated.push_back(image);
}
}
switch (result)
{
case VK_SUCCESS:
@@ -353,6 +403,39 @@ swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
return acquire_result::failed;
}
bool swapchain::latency_sleep(VkSemaphore const Semaphore, std::uint64_t const Value) const
{
if ((false == m_lowlatencyon) || (m_swapchain == VK_NULL_HANDLE) || (vkLatencySleepNV == nullptr))
{
return false;
}
// minimized: nothing is presented, the driver has nothing to measure (the main thread, as glfw wants)
int width{0}, height{0};
glfwGetFramebufferSize(m_window, &width, &height);
if ((width <= 0) || (height <= 0))
{
return false;
}
VkLatencySleepInfoNV info{};
info.sType = VK_STRUCTURE_TYPE_LATENCY_SLEEP_INFO_NV;
info.signalSemaphore = Semaphore;
info.value = Value;
return (vkLatencySleepNV(m_device->handle(), m_swapchain, &info) == VK_SUCCESS);
}
void swapchain::latency_marker(std::uint64_t const PresentId, VkLatencyMarkerNV const Marker) const
{
if ((false == m_lowlatencyon) || (m_swapchain == VK_NULL_HANDLE) || (vkSetLatencyMarkerNV == nullptr))
{
return;
}
VkSetLatencyMarkerInfoNV info{};
info.sType = VK_STRUCTURE_TYPE_SET_LATENCY_MARKER_INFO_NV;
info.presentID = PresentId;
info.marker = Marker;
vkSetLatencyMarkerNV(m_device->handle(), m_swapchain, &info);
}
void swapchain::presented(VkResult const Result)
{
if ((Result == VK_SUBOPTIMAL_KHR) || (Result == VK_ERROR_OUT_OF_DATE_KHR))
@@ -456,6 +539,7 @@ void swapchain::destroy()
}
m_swapchain = VK_NULL_HANDLE;
m_acquiresemaphores.clear();
m_generated.clear();
if ((m_surface != VK_NULL_HANDLE) && (m_instance != nullptr) && (m_instance->handle() != VK_NULL_HANDLE))
{
vkDestroySurfaceKHR(m_instance->handle(), m_surface, nullptr);

View File

@@ -16,6 +16,8 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include <algorithm>
namespace vk14
{
@@ -39,10 +41,31 @@ class swapchain
// the surface of the window and, when the window isn't minimized, the swapchain. Frames: frames in flight
bool create(instance const &Instance, device const &Device, GLFWwindow *Window, std::string const &Name, std::uint32_t const Frames, std::string &Reason);
// before create(): the most images generated (dlss frame generation) shown with each frame drawn, which the swapchain has room
// for, and the low latency mode of reflex (0 off, 1 on, 2 boost; where the device has VK_NV_low_latency2)
void set_generated(std::uint32_t const Count)
{
m_generatedcapacity = Count;
}
void set_low_latency(int const Mode)
{
m_lowlatency = Mode;
}
// reflex is on for the swapchain
bool low_latency() const
{
return m_lowlatencyon;
}
// reflex: the sleep of the driver before the simulation of a frame (once between two presents), Semaphore (a timeline) gets
// Value when it's over; false: no sleep, nothing to wait for. the main thread only
bool latency_sleep(VkSemaphore Semaphore, std::uint64_t Value) const;
// reflex: the point Marker of the frame with the id PresentId reached
void latency_marker(std::uint64_t PresentId, VkLatencyMarkerNV Marker) const;
void destroy();
// the image of the frame Frame (index of the frame in flight)
acquire_result acquire(std::uint32_t const Frame);
// the image of the frame Frame (index of the frame in flight), and Generated more for the frames generated from it, which are
// shown before it (as many of them as there is room for, generated_count())
acquire_result acquire(std::uint32_t const Frame, std::uint32_t const Generated = 0);
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
void record_clear(command_stream *Commands, glm::vec4 const &Colour) const;
// the image acquired and its view
@@ -62,7 +85,7 @@ class swapchain
// the semaphore signalled when the image of the frame Frame is acquired
VkSemaphore acquire_semaphore(std::uint32_t const Frame) const
{
return m_acquiresemaphores[Frame];
return m_acquiresemaphores[Frame * (1 + m_generatedcapacity)];
}
// the semaphore the presentation of the acquired image waits for
VkSemaphore present_semaphore() const
@@ -79,6 +102,40 @@ class swapchain
}
// the result of the presentation of the image: out of date or suboptimal makes the swapchain anew before the next image
void presented(VkResult const Result);
// the most images of the generated frames it can acquire with an image (as made, within what the program may hold at once)
std::uint32_t generated_room() const
{
return std::min(m_generatedcapacity, m_acquirable - 1);
}
// the images acquired for the generated frames of the frame, in the order they are shown
std::uint32_t generated_count() const
{
return static_cast<std::uint32_t>(m_generated.size());
}
VkImage generated_image(std::uint32_t const Index) const
{
return m_images[m_generated[Index]];
}
VkImageView generated_view(std::uint32_t const Index) const
{
return m_views[m_generated[Index]];
}
std::uint32_t generated_index(std::uint32_t const Index) const
{
return m_generated[Index];
}
image_use generated_use(std::uint32_t const Index, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
return image_use{m_images[m_generated[Index]], VK_IMAGE_ASPECT_COLOR_BIT, &m_states[m_generated[Index]], Layout, Stage, Access};
}
VkSemaphore generated_acquire_semaphore(std::uint32_t const Frame, std::uint32_t const Index) const
{
return m_acquiresemaphores[Frame * (1 + m_generatedcapacity) + 1 + Index];
}
VkSemaphore generated_present_semaphore(std::uint32_t const Index) const
{
return m_presentsemaphores[m_generated[Index]];
}
GLFWwindow *window() const
{
@@ -131,9 +188,16 @@ class swapchain
std::vector<VkImageView> m_views;
// one per image: a semaphore given to the presentation can be reused only once the image comes back
std::vector<VkSemaphore> m_presentsemaphores;
// one per frame in flight: free again once the frame which waited for it is done
// one per frame in flight (and one more for each generated frame): free again once the frame which waited for it is done
std::vector<VkSemaphore> m_acquiresemaphores;
std::uint32_t m_image{0};
// the generated frames: room for them, the images acquired for them in this frame
std::uint32_t m_generatedcapacity{0};
std::uint32_t m_acquirable{1};
std::vector<std::uint32_t> m_generated;
// reflex: asked for, and on for the swapchain made
int m_lowlatency{0};
bool m_lowlatencyon{false};
};
} // namespace vk14