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