mirror of
https://github.com/optiscaler/OptiScaler.git
synced 2026-08-28 17:16:46 +00:00
520 lines
17 KiB
C++
520 lines
17 KiB
C++
#include "Streamline_Inputs_Dx12.h"
|
|
#include <Config.h>
|
|
#include <resource_tracking/ResTrack_dx12.h>
|
|
#include <magic_enum.hpp>
|
|
|
|
std::optional<sl::Constants>* Sl_Inputs_Dx12::getFrameData(IFGFeature_Dx12* fgOutput)
|
|
{
|
|
auto frameId = indexToFrameIdMapping[fgOutput->GetIndex()];
|
|
|
|
if (frameId != 0)
|
|
{
|
|
LOG_TRACE("Using SL's frame id: {}", frameId);
|
|
return &slConstants[frameId % BUFFER_COUNT];
|
|
}
|
|
else if (lastConstantsFrameId > lastPresentFrameId)
|
|
{
|
|
LOG_TRACE("Using last reflex present frame id: {}", lastPresentFrameId);
|
|
return &slConstants[lastPresentFrameId % BUFFER_COUNT];
|
|
}
|
|
else
|
|
{
|
|
LOG_TRACE("Using constants frame id: {}", lastConstantsFrameId);
|
|
return &slConstants[lastConstantsFrameId % BUFFER_COUNT];
|
|
}
|
|
}
|
|
|
|
bool Sl_Inputs_Dx12::setConstants(const sl::Constants& values, uint32_t frameId)
|
|
{
|
|
auto fgOutput = reinterpret_cast<IFGFeature_Dx12*>(State::Instance().currentFG);
|
|
|
|
if (fgOutput == nullptr)
|
|
return false;
|
|
|
|
// Streamline already log this error, keep using the previous data
|
|
if (lastConstantsFrameId == frameId)
|
|
return false;
|
|
|
|
lastConstantsFrameId = frameId;
|
|
|
|
auto& data = slConstants[frameId % BUFFER_COUNT];
|
|
|
|
data = sl::Constants {};
|
|
|
|
if (data.has_value())
|
|
{
|
|
if (values.structVersion == data.value().structVersion)
|
|
{
|
|
data = values;
|
|
return true;
|
|
}
|
|
else if ((data.value().structVersion == sl::kStructVersion2 && values.structVersion == sl::kStructVersion1) ||
|
|
values.structVersion == 0)
|
|
// Spider-Man Remastered does this funny thing of sending an invalid struct version
|
|
{
|
|
auto* pNext = data.value().next;
|
|
memcpy(&data, &values, sizeof(values) - sizeof(sl::Constants::minRelativeLinearDepthObjectSeparation));
|
|
data.value().structVersion = sl::kStructVersion2;
|
|
data.value().next = pNext;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
data.reset();
|
|
|
|
LOG_ERROR("Wrong constant struct version");
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Sl_Inputs_Dx12::evaluateState(ID3D12Device* device)
|
|
{
|
|
auto fgOutput = reinterpret_cast<IFGFeature_Dx12*>(State::Instance().currentFG);
|
|
|
|
if (fgOutput == nullptr)
|
|
return false;
|
|
|
|
auto data = getFrameData(fgOutput);
|
|
if (!data->has_value())
|
|
{
|
|
LOG_WARN("Called without constants being set");
|
|
return false;
|
|
}
|
|
|
|
auto& slConstsRef = data->value();
|
|
|
|
static UINT64 lastFrameCount = 0;
|
|
static UINT64 repeatsInRow = 0;
|
|
if (lastFrameCount == fgOutput->FrameCount())
|
|
{
|
|
repeatsInRow++;
|
|
}
|
|
else
|
|
{
|
|
lastFrameCount = fgOutput->FrameCount();
|
|
repeatsInRow = 0;
|
|
}
|
|
|
|
if (repeatsInRow > 10 && fgOutput->IsActive())
|
|
{
|
|
LOG_WARN("Many frame count repeats in a row, stopping FG");
|
|
State::Instance().FGchanged = true;
|
|
repeatsInRow = 0;
|
|
return false;
|
|
}
|
|
|
|
FG_Constants fgConstants {};
|
|
|
|
// TODO
|
|
fgConstants.displayWidth = 0;
|
|
fgConstants.displayHeight = 0;
|
|
|
|
// if ()
|
|
// fgConstants.flags |= FG_Flags::Hdr;
|
|
|
|
if (slConstsRef.depthInverted)
|
|
fgConstants.flags |= FG_Flags::InvertedDepth;
|
|
|
|
if (slConstsRef.motionVectorsJittered)
|
|
fgConstants.flags |= FG_Flags::JitteredMVs;
|
|
|
|
if (slConstsRef.motionVectorsDilated)
|
|
fgConstants.flags |= FG_Flags::DisplayResolutionMVs;
|
|
|
|
if (Config::Instance()->FGAsync.value_or_default())
|
|
fgConstants.flags |= FG_Flags::Async;
|
|
|
|
if (infiniteDepth)
|
|
fgConstants.flags |= FG_Flags::InfiniteDepth;
|
|
|
|
if (Config::Instance()->FGXeFGDepthInverted.value_or_default() != slConstsRef.depthInverted ||
|
|
Config::Instance()->FGXeFGJitteredMV.value_or_default() != slConstsRef.motionVectorsJittered ||
|
|
Config::Instance()->FGXeFGHighResMV.value_or_default() != slConstsRef.motionVectorsDilated)
|
|
{
|
|
Config::Instance()->FGXeFGDepthInverted = slConstsRef.depthInverted;
|
|
Config::Instance()->FGXeFGJitteredMV = slConstsRef.motionVectorsJittered;
|
|
Config::Instance()->FGXeFGHighResMV = slConstsRef.motionVectorsDilated;
|
|
LOG_DEBUG("XeFG DepthInverted: {}", Config::Instance()->FGXeFGDepthInverted.value_or_default());
|
|
LOG_DEBUG("XeFG JitteredMV: {}", Config::Instance()->FGXeFGJitteredMV.value_or_default());
|
|
LOG_DEBUG("XeFG HighResMV: {}", Config::Instance()->FGXeFGHighResMV.value_or_default());
|
|
Config::Instance()->SaveXeFG();
|
|
}
|
|
|
|
fgOutput->EvaluateState(device, fgConstants);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Sl_Inputs_Dx12::reportResource(const sl::ResourceTag& tag, ID3D12GraphicsCommandList* cmdBuffer, uint32_t frameId)
|
|
{
|
|
std::scoped_lock lock(reportResourceMutex);
|
|
|
|
if (!cmdBuffer)
|
|
LOG_TRACE("cmdBuffer is null");
|
|
|
|
auto fgOutput = reinterpret_cast<IFGFeature_Dx12*>(State::Instance().currentFG);
|
|
|
|
// It's possible for only some resources to be marked ready if FGEnabled is enabled during resource tagging
|
|
if (fgOutput == nullptr || !Config::Instance()->FGEnabled.value_or_default())
|
|
return false;
|
|
|
|
bool UIDisabled = !Config::Instance()->DrawUIOverFG.value_or_default();
|
|
|
|
if (tag.type == sl::kBufferTypeUIColorAndAlpha && UIDisabled)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (dispatched)
|
|
{
|
|
fgOutput->StartNewFrame();
|
|
|
|
dispatched = false;
|
|
|
|
indexToFrameIdMapping[fgOutput->GetIndex()] = frameId;
|
|
}
|
|
|
|
// Can cause unforeseen consequences
|
|
static const bool alwaysCopy = false;
|
|
|
|
if (tag.type == sl::kBufferTypeHUDLessColor)
|
|
{
|
|
LOG_TRACE("Hudless lifecycle: {}", magic_enum::enum_name(tag.lifecycle));
|
|
|
|
hudlessSent = true;
|
|
|
|
auto hudlessResource = (ID3D12Resource*) tag.resource->native;
|
|
|
|
auto validity =
|
|
(tag.lifecycle != sl::eOnlyValidNow) ? FG_ResourceValidity::UntilPresent : FG_ResourceValidity::ValidNow;
|
|
|
|
// We need to make sure we have a copy of hudless that WE can use
|
|
// TODO: bugged and copy crashes
|
|
// if (cmdBuffer != nullptr && validity == FG_ResourceValidity::ValidNow)
|
|
// validity = FG_ResourceValidity::ValidButMakeCopy;
|
|
|
|
auto width = tag.extent.width;
|
|
auto height = tag.extent.height;
|
|
|
|
if (!tag.extent)
|
|
{
|
|
const auto desc = hudlessResource->GetDesc();
|
|
width = desc.Width;
|
|
height = desc.Height;
|
|
}
|
|
|
|
Dx12Resource setResource {};
|
|
setResource.type = FG_ResourceType::HudlessColor;
|
|
setResource.cmdList = cmdBuffer;
|
|
setResource.resource = hudlessResource;
|
|
setResource.top = tag.extent.top;
|
|
setResource.left = tag.extent.left;
|
|
setResource.width = width;
|
|
setResource.height = height;
|
|
setResource.state = (D3D12_RESOURCE_STATES) tag.resource->state;
|
|
setResource.validity = validity;
|
|
|
|
fgOutput->SetResource(&setResource);
|
|
|
|
// Assume hudless is the size used for interpolation
|
|
interpolationWidth = width;
|
|
interpolationHeight = height;
|
|
|
|
auto static lastFormat = DXGI_FORMAT_UNKNOWN;
|
|
auto format = hudlessResource->GetDesc().Format;
|
|
|
|
// This might be specific to FSR FG
|
|
if (lastFormat != DXGI_FORMAT_UNKNOWN && lastFormat != format)
|
|
{
|
|
State::Instance().FGchanged = true;
|
|
LOG_DEBUG("HUDLESS format changed, triggering FG reinit");
|
|
}
|
|
|
|
lastFormat = format;
|
|
}
|
|
else if (tag.type == sl::kBufferTypeDepth || tag.type == sl::kBufferTypeHiResDepth ||
|
|
tag.type == sl::kBufferTypeLinearDepth)
|
|
{
|
|
LOG_TRACE("Depth lifecycle: {}, type: {}", magic_enum::enum_name(tag.lifecycle), tag.type);
|
|
|
|
depthSent = true;
|
|
|
|
auto depthResource = (ID3D12Resource*) tag.resource->native;
|
|
|
|
auto width = tag.extent.width;
|
|
auto height = tag.extent.height;
|
|
|
|
if (!tag.extent)
|
|
{
|
|
const auto desc = depthResource->GetDesc();
|
|
width = desc.Width;
|
|
height = desc.Height;
|
|
}
|
|
|
|
const auto validity =
|
|
(tag.lifecycle != sl::eOnlyValidNow) ? FG_ResourceValidity::UntilPresent : FG_ResourceValidity::ValidNow;
|
|
|
|
Dx12Resource setResource {};
|
|
setResource.type = FG_ResourceType::Depth;
|
|
setResource.cmdList = cmdBuffer;
|
|
setResource.resource = depthResource;
|
|
setResource.top = tag.extent.top;
|
|
setResource.left = tag.extent.left;
|
|
setResource.width = width;
|
|
setResource.height = height;
|
|
setResource.state = (D3D12_RESOURCE_STATES) tag.resource->state;
|
|
setResource.validity = validity;
|
|
|
|
fgOutput->SetResource(&setResource);
|
|
}
|
|
else if (tag.type == sl::kBufferTypeMotionVectors)
|
|
{
|
|
LOG_TRACE("MVs lifecycle: {}", magic_enum::enum_name(tag.lifecycle));
|
|
|
|
mvsSent = true;
|
|
|
|
auto mvResource = (ID3D12Resource*) tag.resource->native;
|
|
|
|
auto width = tag.extent.width;
|
|
auto height = tag.extent.height;
|
|
|
|
if (!tag.extent)
|
|
{
|
|
const auto desc = mvResource->GetDesc();
|
|
width = desc.Width;
|
|
height = desc.Height;
|
|
}
|
|
|
|
mvsWidth = width;
|
|
mvsHeight = height;
|
|
|
|
const auto validity =
|
|
(tag.lifecycle != sl::eOnlyValidNow) ? FG_ResourceValidity::UntilPresent : FG_ResourceValidity::ValidNow;
|
|
|
|
Dx12Resource setResource {};
|
|
setResource.type = FG_ResourceType::Velocity;
|
|
setResource.cmdList = cmdBuffer;
|
|
setResource.resource = mvResource;
|
|
setResource.top = tag.extent.top;
|
|
setResource.left = tag.extent.left;
|
|
setResource.width = width;
|
|
setResource.height = height;
|
|
setResource.state = (D3D12_RESOURCE_STATES) tag.resource->state;
|
|
setResource.validity = validity;
|
|
|
|
fgOutput->SetResource(&setResource);
|
|
}
|
|
else if (tag.type == sl::kBufferTypeUIColorAndAlpha)
|
|
{
|
|
LOG_TRACE("UIColorAndAlpha lifecycle: {}", magic_enum::enum_name(tag.lifecycle));
|
|
|
|
uiSent = true;
|
|
|
|
if (uiRequired)
|
|
{
|
|
auto uiResource = (ID3D12Resource*) tag.resource->native;
|
|
|
|
auto width = tag.extent.width;
|
|
auto height = tag.extent.height;
|
|
|
|
if (!tag.extent)
|
|
{
|
|
const auto desc = uiResource->GetDesc();
|
|
width = desc.Width;
|
|
height = desc.Height;
|
|
}
|
|
|
|
const auto validity = (tag.lifecycle != sl::eOnlyValidNow) ? FG_ResourceValidity::UntilPresent
|
|
: FG_ResourceValidity::ValidNow;
|
|
|
|
Dx12Resource setResource {};
|
|
setResource.type = FG_ResourceType::UIColor;
|
|
setResource.cmdList = cmdBuffer;
|
|
setResource.resource = uiResource;
|
|
setResource.top = tag.extent.top;
|
|
setResource.left = tag.extent.left;
|
|
setResource.width = width;
|
|
setResource.height = height;
|
|
setResource.state = (D3D12_RESOURCE_STATES) tag.resource->state;
|
|
setResource.validity = validity;
|
|
|
|
fgOutput->SetResource(&setResource);
|
|
}
|
|
}
|
|
else if (tag.type == sl::kBufferTypeBidirectionalDistortionField)
|
|
{
|
|
LOG_TRACE("DistortionField lifecycle: {}", magic_enum::enum_name(tag.lifecycle));
|
|
|
|
distortionFieldSent = true;
|
|
|
|
auto distortionFieldResource = (ID3D12Resource*) tag.resource->native;
|
|
|
|
auto width = tag.extent.width;
|
|
auto height = tag.extent.height;
|
|
|
|
if (!tag.extent)
|
|
{
|
|
const auto desc = distortionFieldResource->GetDesc();
|
|
width = desc.Width;
|
|
height = desc.Height;
|
|
}
|
|
|
|
const auto validity =
|
|
(tag.lifecycle != sl::eOnlyValidNow) ? FG_ResourceValidity::UntilPresent : FG_ResourceValidity::ValidNow;
|
|
|
|
Dx12Resource setResource {};
|
|
setResource.type = FG_ResourceType::Distortion;
|
|
setResource.cmdList = cmdBuffer;
|
|
setResource.resource = distortionFieldResource;
|
|
setResource.top = tag.extent.top;
|
|
setResource.left = tag.extent.left;
|
|
setResource.width = width;
|
|
setResource.height = height;
|
|
setResource.state = (D3D12_RESOURCE_STATES) tag.resource->state;
|
|
setResource.validity = validity;
|
|
|
|
fgOutput->SetResource(&setResource);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Sl_Inputs_Dx12::dispatchFG()
|
|
{
|
|
dispatched = true;
|
|
|
|
auto fgOutput = reinterpret_cast<IFGFeature_Dx12*>(State::Instance().currentFG);
|
|
if (fgOutput == nullptr)
|
|
return false;
|
|
|
|
auto data = getFrameData(fgOutput);
|
|
if (!data->has_value())
|
|
return false;
|
|
|
|
auto& slConstsRef = data->value();
|
|
|
|
if (State::Instance().FGchanged)
|
|
return false;
|
|
|
|
if (!fgOutput->IsActive())
|
|
return false;
|
|
|
|
// Nukem's function, licensed under GPLv3
|
|
auto loadCameraMatrix = [&]()
|
|
{
|
|
if (data->value().orthographicProjection)
|
|
return false;
|
|
|
|
float projMatrix[4][4];
|
|
memcpy(projMatrix, (void*) &slConstsRef.cameraViewToClip, sizeof(projMatrix));
|
|
|
|
// BUG: Various RTX Remix-based games pass in an identity matrix which is completely useless. No
|
|
// idea why.
|
|
const bool isEmptyOrIdentityMatrix = [&]()
|
|
{
|
|
float m[4][4] = {};
|
|
if (memcmp(projMatrix, m, sizeof(m)) == 0)
|
|
return true;
|
|
|
|
m[0][0] = m[1][1] = m[2][2] = m[3][3] = 1.0f;
|
|
return memcmp(projMatrix, m, sizeof(m)) == 0;
|
|
}();
|
|
|
|
if (isEmptyOrIdentityMatrix)
|
|
return false;
|
|
|
|
// a 0 0 0
|
|
// 0 b 0 0
|
|
// 0 0 c e
|
|
// 0 0 d 0
|
|
const double b = projMatrix[1][1];
|
|
const double c = projMatrix[2][2];
|
|
const double d = projMatrix[3][2];
|
|
const double e = projMatrix[2][3];
|
|
|
|
if (e < 0.0)
|
|
{
|
|
slConstsRef.cameraNear = static_cast<float>((c == 0.0) ? 0.0 : (d / c));
|
|
slConstsRef.cameraFar = static_cast<float>(d / (c + 1.0));
|
|
}
|
|
else
|
|
{
|
|
slConstsRef.cameraNear = static_cast<float>((c == 0.0) ? 0.0 : (-d / c));
|
|
slConstsRef.cameraFar = static_cast<float>(-d / (c - 1.0));
|
|
}
|
|
|
|
if (slConstsRef.depthInverted)
|
|
std::swap(slConstsRef.cameraNear, slConstsRef.cameraFar);
|
|
|
|
slConstsRef.cameraFOV = static_cast<float>(2.0 * std::atan(1.0 / b));
|
|
return true;
|
|
};
|
|
|
|
static bool dontRecalc = false;
|
|
|
|
LOG_TRACE("Camera from SL pre recalc near: {}, far: {}", slConstsRef.cameraNear, slConstsRef.cameraFar);
|
|
|
|
// UE seems to not be passing the correct cameraViewToClip
|
|
// and we can't use it to calculate cameraNear and cameraFar.
|
|
if (engineType != sl::EngineType::eUnreal && !dontRecalc)
|
|
loadCameraMatrix();
|
|
|
|
// Workaround for more games with broken cameraViewToClip
|
|
if (!dontRecalc && (slConstsRef.cameraNear < 0.0f || slConstsRef.cameraFar < 0.0f))
|
|
dontRecalc = true;
|
|
|
|
infiniteDepth = false;
|
|
if (slConstsRef.cameraNear != 0.0f && slConstsRef.cameraFar == 0.0f)
|
|
{
|
|
// A CameraFar value of zero indicates an infinite far plane. Due to a bug in FSR's
|
|
// setupDeviceDepthToViewSpaceDepthParams function, CameraFar must always be greater than
|
|
// CameraNear when in use.
|
|
|
|
infiniteDepth = true;
|
|
slConstsRef.cameraFar = slConstsRef.cameraNear + 1.0f;
|
|
}
|
|
|
|
fgOutput->SetCameraValues(slConstsRef.cameraNear, slConstsRef.cameraFar, slConstsRef.cameraFOV,
|
|
slConstsRef.cameraAspectRatio, 0.0f);
|
|
|
|
fgOutput->SetJitter(slConstsRef.jitterOffset.x, slConstsRef.jitterOffset.y);
|
|
|
|
// Streamline is not 100% clear on if we should multiply by resolution or not.
|
|
// But UE games and Dead Rising expect that multiplication to be done, even if the scale is 1.0.
|
|
// bool multiplyByResolution = dataCopy.mvecScale.x != 1.f || dataCopy.mvecScale.y != 1.f;
|
|
bool multiplyByResolution = true;
|
|
if (multiplyByResolution)
|
|
fgOutput->SetMVScale(slConstsRef.mvecScale.x * mvsWidth, slConstsRef.mvecScale.y * mvsHeight);
|
|
else
|
|
fgOutput->SetMVScale(slConstsRef.mvecScale.x, slConstsRef.mvecScale.y);
|
|
|
|
fgOutput->SetCameraData(
|
|
reinterpret_cast<float*>(&slConstsRef.cameraPos), reinterpret_cast<float*>(&slConstsRef.cameraUp),
|
|
reinterpret_cast<float*>(&slConstsRef.cameraRight), reinterpret_cast<float*>(&slConstsRef.cameraFwd));
|
|
|
|
fgOutput->SetReset(slConstsRef.reset == sl::Boolean::eTrue);
|
|
|
|
fgOutput->SetInterpolationRect(interpolationWidth, interpolationHeight);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Sl_Inputs_Dx12::markLastSendAsRequired()
|
|
{
|
|
uiRequired = uiSent;
|
|
distortionFieldRequired = distortionFieldSent;
|
|
|
|
depthSent = false;
|
|
hudlessSent = false;
|
|
mvsSent = false;
|
|
uiSent = false;
|
|
distortionFieldSent = false;
|
|
}
|
|
|
|
void Sl_Inputs_Dx12::markPresent(uint64_t frameId)
|
|
{
|
|
LOG_TRACE("Present Start: {}", frameId);
|
|
lastPresentFrameId = frameId;
|
|
}
|