AtlasEngine: Fix custom shader time imprecision (#17104)

Since floats are imprecise we need to constrain the time value into a
range that can be accurately represented. Assuming a monitor refresh
rate of 1000 Hz, we can still easily represent 1000 seconds accurately
(roughly 16 minutes). So to solve this, we'll simply treat the shader
time modulo 1000s. This may lead to some unexpected jank every 16min
but it keeps any ongoing animation smooth otherwise.
This commit is contained in:
Leonard Hecker 2024-04-23 18:19:38 +02:00 committed by GitHub
parent a590a1bff0
commit daffb2dbbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 3 deletions

View File

@ -47,6 +47,20 @@ using namespace Microsoft::Console::Render::Atlas;
static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 };
static constexpr D2D1_COLOR_F whiteColor{ 1, 1, 1, 1 };
static u64 queryPerfFreq() noexcept
{
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
return std::bit_cast<u64>(li.QuadPart);
}
static u64 queryPerfCount() noexcept
{
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return std::bit_cast<u64>(li.QuadPart);
}
BackendD3D::BackendD3D(const RenderingPayload& p)
{
THROW_IF_FAILED(p.device->CreateVertexShader(&shader_vs[0], sizeof(shader_vs), nullptr, _vertexShader.addressof()));
@ -485,7 +499,14 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
THROW_IF_FAILED(p.device->CreateSamplerState(&desc, _customShaderSamplerState.put()));
}
_customShaderStartTime = std::chrono::steady_clock::now();
// Since floats are imprecise we need to constrain the time value into a range that can be accurately represented.
// Assuming a monitor refresh rate of 1000 Hz, we can still easily represent 1000 seconds accurately (roughly 16 minutes).
// 10000 seconds would already result in a 50% error. So to avoid this, we use queryPerfCount() modulo _customShaderPerfTickMod.
// The use of a power of 10 is intentional, because shaders are often periodic and this makes any decimal multiplier up to 3 fractional
// digits not break the periodicity. For instance, with a wraparound of 1000 seconds sin(1.234*x) is still perfectly periodic.
const auto freq = queryPerfFreq();
_customShaderPerfTickMod = freq * 1000;
_customShaderSecsPerPerfTick = 1.0f / freq;
}
}
@ -2111,8 +2132,12 @@ void BackendD3D::_debugDumpRenderTarget(const RenderingPayload& p)
void BackendD3D::_executeCustomShader(RenderingPayload& p)
{
{
// See the comment in _recreateCustomShader() which initializes the two members below and explains what they do.
const auto now = queryPerfCount();
const auto time = static_cast<int>(now % _customShaderPerfTickMod) * _customShaderSecsPerPerfTick;
const CustomConstBuffer data{
.time = std::chrono::duration<f32>(std::chrono::steady_clock::now() - _customShaderStartTime).count(),
.time = time,
.scale = static_cast<f32>(p.s->font->dpi) / static_cast<f32>(USER_DEFAULT_SCREEN_DPI),
.resolution = {
static_cast<f32>(_viewportCellCount.x * p.s->font->cellSize.x),

View File

@ -248,7 +248,8 @@ namespace Microsoft::Console::Render::Atlas
wil::com_ptr<ID3D11SamplerState> _customShaderSamplerState;
wil::com_ptr<ID3D11Texture2D> _customShaderTexture;
wil::com_ptr<ID3D11ShaderResourceView> _customShaderTextureView;
std::chrono::steady_clock::time_point _customShaderStartTime;
u64 _customShaderPerfTickMod = 0;
f32 _customShaderSecsPerPerfTick = 0;
wil::com_ptr<ID3D11Texture2D> _backgroundBitmap;
wil::com_ptr<ID3D11ShaderResourceView> _backgroundBitmapView;

View File

@ -146,6 +146,8 @@ namespace Microsoft::Console::Render::Atlas
using i32x4 = vec4<i32>;
using i32r = rect<i32>;
using u64 = uint64_t;
using f32 = float;
using f32x2 = vec2<f32>;
using f32x4 = vec4<f32>;