diff --git a/CMakeLists.txt b/CMakeLists.txt index f5cd8b3c..21ceb453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +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_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" OFF) 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) @@ -469,7 +469,8 @@ if (WITH_VULKAN) 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) + # static library of ngx to build with, the dlss library next to the program to run with (rendering/vulkan14/vulkan14dlss.h), and the + # one of its frame generation (gfx.framegeneration) where the sdk has it 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") @@ -491,11 +492,13 @@ if (WITH_VULKAN) set(DLSS_NGX_LIBRARY "$,${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(DLSSG_RUNTIME_PATTERN "nvngx_dlssg.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(DLSSG_RUNTIME_PATTERN "libnvidia-ngx-dlssg.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") @@ -508,6 +511,14 @@ if (WITH_VULKAN) 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() + file(GLOB DLSSG_RUNTIME_RELEASE "${DLSS_RUNTIME_DIR}/rel/${DLSSG_RUNTIME_PATTERN}") + file(GLOB DLSSG_RUNTIME_DEVELOPMENT "${DLSS_RUNTIME_DIR}/dev/${DLSSG_RUNTIME_PATTERN}") + if (DLSSG_RUNTIME_RELEASE) + list(APPEND DLSS_RUNTIME_RELEASE ${DLSSG_RUNTIME_RELEASE}) + else() + message(STATUS "WITH_DLSS=ON: no ${DLSSG_RUNTIME_PATTERN} in ${DLSS_RUNTIME_DIR}/rel, the frame generation (gfx.framegeneration) needs it next to the program") + endif() + list(APPEND DLSS_RUNTIME_DEVELOPMENT ${DLSSG_RUNTIME_DEVELOPMENT}) # 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() diff --git a/application/application.cpp b/application/application.cpp index 1b6d00ca..4f213359 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -496,6 +496,7 @@ int eu07_application::run() while (!glfwWindowShouldClose(m_windows.front()) && !m_modestack.empty()) { Timer::subsystem.mainloop_total.start(); + GfxRenderer->Frame_Start(); Timer::subsystem.loop_input.start(); glfwPollEvents(); diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 8e0484c9..cd6ce644 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -102,16 +102,27 @@ the same entry. | `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) | +| `gfx.framegeneration off/x2/x3/x4` | `off` | DLSS Frame Generation of the Vulkan renderer, with `dlss` or `dlaa`: one (`x2`) to three (`x4`) frames made by DLSS between two frames drawn, shown before the frame drawn and spaced over the time of a frame. Needs a GeForce RTX of the 40 series (`x2`) or the 50 series (`x3`, `x4`; a gpu which can't is given the most it can) and `nvngx_dlssg.dll` / `libnvidia-ngx-dlssg.so.` next to the program; otherwise the frames aren't generated (the log says why). Turns on Reflex. The presents are spaced by the render thread (`gfx.vulkan.renderthread`); without it they go at once, which works with vsync. The motion blur is shortened as many times. Read at the start | +| `gfx.reflex off/on/boost` | `off` | NVIDIA Reflex of the Vulkan renderer (`VK_NV_low_latency2`): the driver keeps the cpu from running ahead of the gpu, the main loop waits for it before the input of a frame; `boost` keeps the clocks of the gpu up too. On by itself with `gfx.framegeneration`. Read at the start | 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=` for another place). The build copies `nvngx_dlss.dll` (Windows) or `libnvidia-ngx-dlss.so.` (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 +with `nvngx_dlssg.dll` / `libnvidia-ngx-dlssg.so.` of the frame generation, and the development libraries into +`ngx_dev/`; a program copied elsewhere needs the libraries 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. +The frame generation is given the frame of the main window with the user interface and without it (the "hudless", so it +tells the user interface apart), the depth and the velocities of the scene turned the way up of the window, and the +camera without the jitter. It makes its frames into images of its own, copied to images of the swapchain acquired with +the frame (the swapchain of the main window has one image more for each of them). If it fails, the frames it was to make +are the frame drawn and it stays off. Its debug keys (Ctrl+Shift+V, Ctrl+Shift+T, see the DLSS-FG programming guide) are +in the development library (`gfx.dlss.developer yes`); the debug window can turn the clip space of its matrices over, +should its test of the matrices show the camera motion the wrong way. + ### Ambient occlusion Darkening of creases, corners and the ground under objects, worked out from the depth of the image (screen space) diff --git a/rendering/common/temporalaa.cpp b/rendering/common/temporalaa.cpp index 9852e554..51358c33 100644 --- a/rendering/common/temporalaa.cpp +++ b/rendering/common/temporalaa.cpp @@ -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; diff --git a/rendering/common/temporalaa.h b/rendering/common/temporalaa.h index ba98403f..7b35f895 100644 --- a/rendering/common/temporalaa.h +++ b/rendering/common/temporalaa.h @@ -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}; }; diff --git a/rendering/renderer.h b/rendering/renderer.h index 930f2cd5..30f6bd09 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -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; diff --git a/rendering/vulkan14/vulkan14context.h b/rendering/vulkan14/vulkan14context.h index 9f9467a5..559287e5 100644 --- a/rendering/vulkan14/vulkan14context.h +++ b/rendering/vulkan14/vulkan14context.h @@ -40,6 +40,7 @@ http://mozilla.org/MPL/2.0/. #include #include +#include #include #include #include @@ -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 backbuffer; + std::shared_ptr hudless; + std::shared_ptr depth; + std::shared_ptr motion; + std::array, 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 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 textures; // [0]: the main window, then the windows of the extra viewports @@ -503,6 +535,10 @@ struct vulkan14_renderer::context std::vector signals; // the windows presented once the frame is submitted (the render thread) std::vector 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 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 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 diff --git a/rendering/vulkan14/vulkan14device.cpp b/rendering/vulkan14/vulkan14device.cpp index 3530eba4..fa3e6312 100644 --- a/rendering/vulkan14/vulkan14device.cpp +++ b/rendering/vulkan14/vulkan14device.cpp @@ -325,6 +325,40 @@ bool device::create(instance const &Instance, std::string &Reason, std::vector 0) || (Global.gfx_framegeneration > 1)) + { + std::uint32_t count{0}; + vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, nullptr); + std::vector 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(create.pNext); + create.pNext = &presentid; + } create.queueCreateInfoCount = static_cast(queues.size()); create.pQueueCreateInfos = queues.data(); create.enabledExtensionCount = static_cast(extensions.size()); @@ -382,6 +422,10 @@ bool device::create(instance const &Instance, std::string &Reason, std::vector #include +#include #endif #include +#include #include +#include #include #include @@ -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 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 = ®ion; + vkCmdCopyImage2(Buffer, ©); + } + VkMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT; + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.memoryBarrierCount = 1; + dependency.pMemoryBarriers = &barrier; + vkCmdPipelineBarrier2(Buffer, &dependency); + } + + void fail_generation(std::string const &Error) + { + if (generation_error.empty()) + { + generation_error = Error; + } + generation_failed = true; + } + + // the replay of the frame: the feature of the frame generation made when it isn't there, then evaluated once for each frame it + // makes, from the first one shown on + static void record_generation(VkCommandBuffer const Buffer, void *const Data) + { + auto &self{*static_cast(Data)}; + if (self.generation_failed || (self.generation_parameters == nullptr) || self.generated.empty()) + { + self.copy_backbuffer(Buffer); + return; + } + auto *parameters{self.generation_parameters}; + if (self.generation_handle == nullptr) + { + NVSDK_NGX_DLSSG_Create_Params create{}; + create.Width = static_cast(self.generation_wanted.output.x); + create.Height = static_cast(self.generation_wanted.output.y); + create.NativeBackbufferFormat = static_cast(self.generation_wanted.format); + create.RenderWidth = static_cast(self.generation_wanted.render.x); + create.RenderHeight = static_cast(self.generation_wanted.render.y); + create.DynamicResolutionScaling = false; + // the helper of vulkan leaves out the size of the depth and the velocities, which the one of d3d12 gives + NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Width, create.Width); + NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_Height, create.Height); + NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalWidth, create.RenderWidth); + NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_InternalHeight, create.RenderHeight); + NVSDK_NGX_Parameter_SetUI(parameters, NVSDK_NGX_DLSSG_Parameter_DynamicResolution, 0); + auto const result{NGX_VK_CREATE_DLSSG(Buffer, 1, 1, &self.generation_handle, parameters, &create)}; + if (NVSDK_NGX_FAILED(result) || (self.generation_handle == nullptr)) + { + self.generation_handle = nullptr; + self.fail_generation("the feature of the frame generation of dlss can't be made: " + result_text(result)); + self.copy_backbuffer(Buffer); + return; + } + self.generation_made = self.generation_wanted; + } + auto backbuffer{wrap(self.backbuffer, true)}; + auto hudless{wrap(self.hudless, true)}; + auto depth{wrap(self.generation_depth, true)}; + auto motion{wrap(self.generation_motion, true)}; + auto options{self.generation_options}; + options.multiFrameCount = static_cast(self.generated.size()); + for (std::size_t i = 0; i < self.generated.size(); ++i) + { + auto output{wrap(self.generated[i], true)}; + NVSDK_NGX_VK_DLSSG_Eval_Params evaluation{}; + evaluation.pBackbuffer = &backbuffer; + evaluation.pDepth = &depth; + evaluation.pMVecs = &motion; + evaluation.pHudless = (self.hudless.valid ? &hudless : nullptr); + evaluation.pOutputInterpFrame = &output; + options.multiFrameIndex = static_cast(i + 1); + auto const result{NGX_VK_EVALUATE_DLSSG(Buffer, self.generation_handle, parameters, &evaluation, &options)}; + if (NVSDK_NGX_FAILED(result)) + { + self.fail_generation("the frame generation of dlss can't be evaluated: " + result_text(result)); + self.copy_backbuffer(Buffer); + return; + } + } + ++self.generations; + } + + void release_generation(deletion_queue *Deletions, std::uint64_t const Value) + { + if (generation_handle == nullptr) + { + return; + } + auto *old{generation_handle}; + generation_handle = nullptr; + generation_made = generation_feature{}; + if (Deletions != nullptr) + { + Deletions->push(Value, [old]() { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); + } + else + { + NVSDK_NGX_VULKAN_ReleaseFeature(old); + } + } + void release(deletion_queue *Deletions, std::uint64_t const Value) { if (handle == nullptr) @@ -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 wanted_features() +{ + std::vector features{NVSDK_NGX_Feature_SuperSampling}; + if (Global.gfx_framegeneration > 1) + { + features.push_back(NVSDK_NGX_Feature_FrameGeneration); + } + return features; +} + +void add_extensions(VkExtensionProperties const *Properties, std::uint32_t const Count, std::vector &Extensions) +{ + for (std::uint32_t i = 0; i < Count; ++i) + { + std::string const name{Properties[i].extensionName}; + if (std::find(Extensions.begin(), Extensions.end(), name) == Extensions.end()) + { + Extensions.push_back(name); + } + } +} + +} // namespace + void dlss::instance_extensions(std::vector &Extensions) { state temporary; temporary.prepare_paths(); - 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(Inputs.backbuffer->desc().extent.width), static_cast(Inputs.backbuffer->desc().extent.height)}; + feature.render = {static_cast(Inputs.depth->desc().extent.width), static_cast(Inputs.depth->desc().extent.height)}; + feature.format = Inputs.backbuffer->desc().format; + if ((s.generation_handle != nullptr) && (false == (feature == s.generation_made))) + { + s.release_generation(&Deletions, Value); + } + s.generation_wanted = feature; + auto const describe = [](image const *Image, state::resource &Resource) { + Resource.valid = (Image != nullptr); + if (Image == nullptr) + { + return; + } + Resource.image = Image->handle(); + Resource.view = Image->view(); + Resource.format = Image->desc().format; + Resource.aspect = Image->aspect(); + Resource.width = Image->desc().extent.width; + Resource.height = Image->desc().extent.height; + }; + describe(Inputs.backbuffer, s.backbuffer); + describe(Inputs.hudless, s.hudless); + describe(Inputs.depth, s.generation_depth); + describe(Inputs.motion, s.generation_motion); + s.generated.resize(std::min(Inputs.outputs.size(), s.generation_max)); + for (std::size_t i = 0; i < s.generated.size(); ++i) + { + describe(Inputs.outputs[i], s.generated[i]); + } + + auto &options{s.generation_options}; + options = NVSDK_NGX_DLSSG_Opt_Eval_Params{}; + // the clip space of the renderer has its rows from the bottom of the picture up, as the images; the images the frame generation + // is given are turned over, and its clip space (that of d3d12, y up the picture) is the one of the renderer as it is. the debug + // window turns it over for the other way + glm::mat4 flip{1.f}; + if (s.generation_clip_flip) + { + flip[1][1] = -1.f; + } + auto const viewtoclip{flip * Inputs.view_to_clip}; + auto const cliptoprevious{flip * Inputs.clip_to_previous_clip * flip}; + ngx_matrix(viewtoclip, options.cameraViewToClip); + ngx_matrix(glm::inverse(viewtoclip), options.clipToCameraView); + ngx_matrix(glm::mat4{1.f}, options.clipToLensClip); + ngx_matrix(cliptoprevious, options.clipToPrevClip); + ngx_matrix(glm::inverse(cliptoprevious), options.prevClipToClip); + // the offset as dlss takes it, along the rows turned over + auto const jitter{Inputs.jitter * glm::vec2{s.flip_x ? -1.f : 1.f, s.flip_y ? 1.f : -1.f}}; + options.jitterOffset[0] = jitter.x; + options.jitterOffset[1] = jitter.y; + // the velocities are parts of the image along the rows of the renderer: pixels of the image turned over + options.mvecScale[0] = static_cast(s.generation_depth.width); + options.mvecScale[1] = -static_cast(s.generation_depth.height); + options.cameraPinholeOffset[0] = 0.f; + options.cameraPinholeOffset[1] = 0.f; + for (int i = 0; i < 3; ++i) + { + options.cameraPos[i] = Inputs.position[i]; + options.cameraUp[i] = Inputs.up[i]; + options.cameraRight[i] = Inputs.right[i]; + options.cameraFwd[i] = Inputs.forward[i]; + } + options.cameraNear = Inputs.znear; + options.cameraFar = Inputs.zfar; + options.cameraFOV = Inputs.fov; + options.cameraAspectRatio = Inputs.aspect; + options.colorBuffersHDR = false; + options.depthInverted = true; + options.cameraMotionIncluded = true; + options.reset = Inputs.reset || s.generation_reset_wanted; + s.generation_reset_wanted = false; + options.automodeOverrideReset = false; + options.notRenderingGameFrames = false; + options.orthoProjection = false; + // no value the velocities have marks them as missing + options.motionVectorsInvalidValue = FLT_MAX; + options.motionVectorsDilated = false; + options.menuDetectionEnabled = false; + options.mvecsSubrectSize = {s.generation_motion.width, s.generation_motion.height}; + options.depthSubrectSize = {s.generation_depth.width, s.generation_depth.height}; + options.hudLessSubrectSize = {s.hudless.width, s.hudless.height}; + options.backbufferSubrectSize = {s.backbuffer.width, s.backbuffer.height}; + options.outputInterpSubrectSize = {s.backbuffer.width, s.backbuffer.height}; + Commands.external(&state::record_generation, &s); +} + std::string dlss::sync() { auto &s{*m_state}; @@ -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 {}; diff --git a/rendering/vulkan14/vulkan14dlss.h b/rendering/vulkan14/vulkan14dlss.h index 3edb3c58..75382dc9 100644 --- a/rendering/vulkan14/vulkan14dlss.h +++ b/rendering/vulkan14/vulkan14dlss.h @@ -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 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 diff --git a/rendering/vulkan14/vulkan14frame.cpp b/rendering/vulkan14/vulkan14frame.cpp index 428dc147..88d34d3b 100644 --- a/rendering/vulkan14/vulkan14frame.cpp +++ b/rendering/vulkan14/vulkan14frame.cpp @@ -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 &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(); + 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(targets.dlss_motion)) || (false == static_cast(targets.depth))) + { + return; + } + auto const render{targets.extent}; + if ((false == static_cast(generation.depth)) || (generation.render.width != render.width) || (generation.render.height != render.height)) + { + auto make = [&](std::shared_ptr &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(); + 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(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 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 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 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(extent.width), static_cast(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 = ®ion; + 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 = ®ion; + ctx.commands->copy_image2(©); + }; + 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 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(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(window.format()), (targets.srgb && (false == window.srgb())) ? 1 : 0)}; + auto const pipeline{post_pipeline("present.frag", static_cast(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(pipeline)); commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); std::array descriptors; diff --git a/rendering/vulkan14/vulkan14imgui.cpp b/rendering/vulkan14/vulkan14imgui.cpp index 4018ffb3..e54be045 100644 --- a/rendering/vulkan14/vulkan14imgui.cpp +++ b/rendering/vulkan14/vulkan14imgui.cpp @@ -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; diff --git a/rendering/vulkan14/vulkan14imgui.h b/rendering/vulkan14/vulkan14imgui.h index 38d27852..ade4e728 100644 --- a/rendering/vulkan14/vulkan14imgui.h +++ b/rendering/vulkan14/vulkan14imgui.h @@ -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() { diff --git a/rendering/vulkan14/vulkan14renderer.cpp b/rendering/vulkan14/vulkan14renderer.cpp index 85442207..c876318e 100644 --- a/rendering/vulkan14/vulkan14renderer.cpp +++ b/rendering/vulkan14/vulkan14renderer.cpp @@ -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(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((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(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(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(Job.waits.size()); submit.pWaitSemaphoreInfos = Job.waits.data(); submit.commandBufferInfoCount = static_cast(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 const &Windows) +bool vulkan14_renderer::context::present(std::vector 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(generated + 1), std::max(0.0, 0.95 * present_interval - submit_time) / static_cast(generated)); + } + auto const step{std::chrono::duration_cast(std::chrono::duration(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 = ℑ + 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 waits; std::vector swapchains; std::vector images; + std::vector presentids; std::vector 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(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(waits.size()); info.pWaitSemaphores = waits.data(); info.swapchainCount = static_cast(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(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(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 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 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 lock(render_mutex); + render_idle.wait(lock, [this]() { return (false == render_queued) || render_submitted; }); + render_wait = std::chrono::duration(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(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::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()}; + // 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; diff --git a/rendering/vulkan14/vulkan14renderer.h b/rendering/vulkan14/vulkan14renderer.h index 6a7cf859..c02010c6 100644 --- a/rendering/vulkan14/vulkan14renderer.h +++ b/rendering/vulkan14/vulkan14renderer.h @@ -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; diff --git a/rendering/vulkan14/vulkan14scene.cpp b/rendering/vulkan14/vulkan14scene.cpp index 525b1c29..ba9f0cba 100644 --- a/rendering/vulkan14/vulkan14scene.cpp +++ b/rendering/vulkan14/vulkan14scene.cpp @@ -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(); diff --git a/rendering/vulkan14/vulkan14swapchain.cpp b/rendering/vulkan14/vulkan14swapchain.cpp index 5b82d03c..46e3c226 100644 --- a/rendering/vulkan14/vulkan14swapchain.cpp +++ b/rendering/vulkan14/vulkan14swapchain.cpp @@ -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); diff --git a/rendering/vulkan14/vulkan14swapchain.h b/rendering/vulkan14/vulkan14swapchain.h index f028d3c9..855299ce 100644 --- a/rendering/vulkan14/vulkan14swapchain.h +++ b/rendering/vulkan14/vulkan14swapchain.h @@ -16,6 +16,8 @@ http://mozilla.org/MPL/2.0/. #include "rendering/vulkan14/vulkan14common.h" #include "rendering/vulkan14/vulkan14passes.h" +#include + 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(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 m_views; // one per image: a semaphore given to the presentation can be reused only once the image comes back std::vector 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 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 m_generated; + // reflex: asked for, and on for the swapchain made + int m_lowlatency{0}; + bool m_lowlatencyon{false}; }; } // namespace vk14 diff --git a/shaders/vk14/postfx_fg_flip.frag b/shaders/vk14/postfx_fg_flip.frag new file mode 100644 index 00000000..b9be55e2 --- /dev/null +++ b/shaders/vk14/postfx_fg_flip.frag @@ -0,0 +1,18 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#define LAYOUT_POST 1 +#include "common.glsl" +// the depth or the velocities of the scene for the frame generation of dlss (rendering/vulkan14/vulkan14dlss.h), turned the right +// way up: the images of the renderer go from the bottom of the picture up (decision K8), the image the frame generation is given +// (what the window shows) from the top down. the texel as it is; the velocities keep their sign, their scale turns them over +layout(location = 0) out vec4 out_value; + +#pragma texture(source_tex, 0, RGBA) +layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D source_tex; + +void main() +{ + ivec2 size = textureSize(source_tex, 0); + ivec2 pixel = ivec2(gl_FragCoord.xy); + out_value = texelFetch(source_tex, ivec2(pixel.x, size.y - 1 - pixel.y), 0); +} diff --git a/tools/fetch_dlss_sdk.cmake b/tools/fetch_dlss_sdk.cmake index df84568d..c0fca521 100644 --- a/tools/fetch_dlss_sdk.cmake +++ b/tools/fetch_dlss_sdk.cmake @@ -1,5 +1,5 @@ # 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 +# static library of NGX and the libraries of DLSS and of its frame generation 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=] [-DDLSS_SDK_TAG=] [-DDLSS_SDK_PLATFORMS=windows;linux] [-DDLSS_SDK_ARCH=x86_64|aarch64] @@ -36,8 +36,8 @@ 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) +# builds with, /MD and /MDd) with the release and the development libraries of dlss and of its frame generation (the latter show +# the indicator and take the debug keys, gfx.dlss.developer) set(patterns "/include/" "/LICENSE.txt") foreach(platform IN LISTS DLSS_SDK_PLATFORMS) if(platform STREQUAL "windows") @@ -50,12 +50,16 @@ foreach(platform IN LISTS DLSS_SDK_PLATFORMS) "${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") + "/lib/Windows_${DLSS_SDK_ARCH}/dev/nvngx_dlss.dll" + "/lib/Windows_${DLSS_SDK_ARCH}/rel/nvngx_dlssg.dll" + "/lib/Windows_${DLSS_SDK_ARCH}/dev/nvngx_dlssg.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.*") + "/lib/Linux_${DLSS_SDK_ARCH}/dev/libnvidia-ngx-dlss.so.*" + "/lib/Linux_${DLSS_SDK_ARCH}/rel/libnvidia-ngx-dlssg.so.*" + "/lib/Linux_${DLSS_SDK_ARCH}/dev/libnvidia-ngx-dlssg.so.*") else() message(FATAL_ERROR "unknown platform \"${platform}\" in DLSS_SDK_PLATFORMS, expected windows or linux") endif() diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 67f9f92b..6a14dcef 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1962,6 +1962,22 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To 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.framegeneration" ) { + Parser.getTokens( 1 ); + std::string mode; + Parser >> mode; + std::transform( mode.begin(), mode.end(), mode.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + gfx_framegeneration = ( mode == "x2" || mode == "on" || mode == "yes" || mode == "2" ) ? 2 : ( mode == "x3" || mode == "3" ) ? 3 : ( mode == "x4" || mode == "4" ) ? 4 : 1; + if( ( gfx_framegeneration == 1 ) && ( mode != "off" ) && ( mode != "no" ) && ( mode != "1" ) ) { + ErrorLog( "Bad config: unknown gfx.framegeneration \"" + mode + "\", expected off, x2, x3 or x4" ); } } + else if( Token == "gfx.reflex" ) { + Parser.getTokens( 1 ); + std::string mode; + Parser >> mode; + std::transform( mode.begin(), mode.end(), mode.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + gfx_reflex = ( mode == "on" || mode == "yes" || mode == "1" ) ? 1 : ( mode == "boost" || mode == "2" ) ? 2 : 0; + if( ( gfx_reflex == 0 ) && ( mode != "off" ) && ( mode != "no" ) && ( mode != "0" ) ) { + ErrorLog( "Bad config: unknown gfx.reflex \"" + mode + "\", expected off, on or boost" ); } } 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 ); } @@ -2103,6 +2119,8 @@ global_settings::export_as_text( std::ostream &Output ) const { 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, "gfx.framegeneration", std::string{ gfx_framegeneration > 1 ? "x" + std::to_string( gfx_framegeneration ) : "off" } ); + export_as_text( Output, "gfx.reflex", std::string{ gfx_reflex == 2 ? "boost" : gfx_reflex == 1 ? "on" : "off" } ); export_as_text( Output, "latitude", fLatitudeDeg ); export_as_text( Output, "convertmodels", iConvertModels ); export_as_text( Output, "file.binary.terrain", file_binary_terrain ); diff --git a/utilities/Globals.h b/utilities/Globals.h index a341d1fa..dfe95138 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -206,6 +206,12 @@ struct global_settings { 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 }; + // gfx.framegeneration off, x2, x3, x4: the frames shown for each frame drawn (dlss frame generation of the vulkan renderer, with + // gfx.antialiasing dlss or dlaa; x3 and x4 where the gpu has multi frame generation, else x2). 1: off. read at the start + int gfx_framegeneration{ 1 }; + // gfx.reflex off, on, boost: nvidia reflex of the vulkan renderer (VK_NV_low_latency2), the frame begun as late as the gpu + // allows; on at least with the frame generation. read at the start + int gfx_reflex{ 0 }; 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