Skip to content

Commit

Permalink
AtlasEngine: Fix custom shader time imprecision (#17104)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lhecker authored Apr 23, 2024
1 parent a590a1b commit daffb2d
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 3 deletions.
29 changes: 27 additions & 2 deletions src/renderer/atlas/BackendD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/atlas/BackendD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/atlas/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down

0 comments on commit daffb2d

Please sign in to comment.