Render row-by-row instead of invalidating entire screen (#5185)

## Summary of the Pull Request
Adjusts DirectX renderer to use `til::bitmap` to track invalidation
regions. Uses special modification to invalidate a row-at-a-time to
ensure ligatures and NxM glyphs continue to work.

## References
Likely helps #1064

## PR Checklist
* [x] Closes #778
* [x] I work here.
* [x] Manual testing performed. See Performance traces in #778.
* [x] Automated tests for `til` changes.
* [x] Am core contributor. And discussed with @DHowett-MSFT.

## Detailed Description of the Pull Request / Additional comments
- Applies `til::bitmap` as the new invalidation scheme inside the
  DirectX renderer and updates all entrypoints for collecting
  invalidation data to coalesce into this structure.
- Semi-permanently routes all invalidations through a helper method
  `_InvalidateRectangle` that will expand any invalidation to cover the
  entire line. This ensures that ligatures and NxM glyphs will continue
  to render appropriately while still allowing us to dramatically reduce
  the number of lines drawn overall. In the future, we may come up with
  a tighter solution than line-by-line invalidation and can modify this
  helper method appropriately at that later date to further scope the
  invalid region.
- Ensures that the `experimental.retroTerminalEffects` feature continues
  to invalidate the entire display on start of frame as the shader is
  applied at the end of the frame composition and will stack on itself
  in an amusing fashion when we only redraw part of the display.
- Moves many member variables inside the DirectX renderer into the new
  `til::size`, `til::point`, and `til::rectangle` methods to facilitate
  easier management and mathematical operations. Consequently adds
  `try/catch` blocks around many of the already-existing `noexcept`
  methods to deal with mathematical or casting failures now detected by
  using the support classes.
- Corrects `TerminalCore` redraw triggers to appropriately communicate
  scrolling circumstances to the renderer so it can optimize the draw
  regions appropriately.
- Fixes an issue in the base `Renderer` that was causing overlapping
  scroll regions due to behavior of `Viewport::TrimToViewport` modifying
  the local. This fix is "good enough" for now and should go away when
  `Viewport` is fully migrated to `til::rectangle`.
- Adds multiplication and division operators to `til::rectangle` and
  supporting tests. These operates will help scale back and forth
  between a cell-based rectangle and a pixel-based rectangle. They take
  special care to ensure that a pixel rectangle being divided downward
  back to cells will expand (with the ceiling division methods) to cover
  a full cell when even one pixel inside the cell is touched (as is how
  a redraw would have to occur).
- Blocks off trace logging of invalid regions if no one is listening to
  optimize performance.
- Restores full usage of `IDXGISwapChain1::Present1` to accurately and
  fully communicate dirty and scroll regions to the underlying DirectX
  framework. This additional information allows the framework to
  optimize drawing between frames by eliminating data transfer of
  regions that aren't modified and shuffling frames in place. See
  [Remarks](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgiswapchain1-present1#remarks)
  for more details.
- Updates `til::bitmap` set methods to use more optimized versions of
  the setters on the `dynamic_bitset<>` that can bulk fill bits as the
  existing algorithm was noticeably slow after applying the
  "expand-to-row" helper to the DirectX renderer invalidation.
- All `til` import hierarchy is now handled in the parent `til.h` file
  and not in the child files to prevent circular imports from happening.
  We don't expect the import of any individual library file, only the
  base one. So this should be OK for now.

## Validation Steps Performed
- Ran `cmatrix`, `cmatrix -u0`, and `cacafire` after changes were made.
- Made a bunch of ligatures with `Cascadia Code` in the Terminal
  before/after the changes and confirmed they still ligate.
- Ran `dir` in Powershell and fixed the scrolling issues
- Clicked all over the place and dragged to make sure selection works.
- Checked retro terminal effect manually with Powershell.
This commit is contained in:
Michael Niksa 2020-04-13 13:09:02 -07:00 committed by GitHub
parent ffbdfe32ac
commit 79684bf821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 764 additions and 433 deletions

View file

@ -938,6 +938,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
auto lock = _terminal->LockForWriting();
const auto cursorPosition = point.Position();
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
@ -979,6 +981,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
}
_renderer->TriggerSelection();
}
else if (point.Properties().IsRightButtonPressed())
@ -1037,6 +1040,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
auto lock = _terminal->LockForWriting();
const auto cursorPosition = point.Position();
if (_singleClickTouchdownPos)

View file

@ -780,7 +780,11 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
if (notifyScroll)
{
_buffer->GetRenderTarget().TriggerRedrawAll();
// We have to report the delta here because we might have circled the text buffer.
// That didn't change the viewport and therefore the TriggerScroll(void)
// method can't detect the delta on its own.
COORD delta{ 0, -gsl::narrow<SHORT>(newRows) };
_buffer->GetRenderTarget().TriggerScroll(&delta);
_NotifyScrollEvent();
}
@ -789,13 +793,20 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
void Terminal::UserScrollViewport(const int viewTop)
{
// we're going to modify state here that the renderer could be reading.
auto lock = LockForWriting();
const auto clampedNewTop = std::max(0, viewTop);
const auto realTop = ViewStartIndex();
const auto newDelta = realTop - clampedNewTop;
// if viewTop > realTop, we want the offset to be 0.
_scrollOffset = std::max(0, newDelta);
_buffer->GetRenderTarget().TriggerRedrawAll();
// We can use the void variant of TriggerScroll here because
// we adjusted the viewport so it can detect the difference
// from the previous frame drawn.
_buffer->GetRenderTarget().TriggerScroll();
}
int Terminal::GetScrollOffset() noexcept

View file

@ -11,8 +11,8 @@
#include "til/some.h"
#include "til/size.h"
#include "til/point.h"
#include "til/rectangle.h"
#include "til/operators.h"
#include "til/rectangle.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"

View file

@ -273,7 +273,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
THROW_HR_IF(E_INVALIDARG, !_rc.contains(pt));
_runs.reset(); // reset cached runs on any non-const method
til::at(_bits, _rc.index_of(pt)) = true;
_bits.set(_rc.index_of(pt));
_dirty |= til::rectangle{ pt };
}
@ -283,9 +283,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
THROW_HR_IF(E_INVALIDARG, !_rc.contains(rc));
_runs.reset(); // reset cached runs on any non-const method
for (const auto pt : rc)
for (auto row = rc.top(); row < rc.bottom(); ++row)
{
til::at(_bits, _rc.index_of(pt)) = true;
_bits.set(_rc.index_of(til::point{ rc.left(), row }), rc.width(), true);
}
_dirty |= rc;
@ -378,6 +378,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return _dirty == _rc;
}
constexpr til::size size() const noexcept
{
return _sz;
}
std::wstring to_string() const
{
std::wstringstream wss;

View file

@ -3,10 +3,6 @@
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// Operators go here when they involve two headers that can't/don't include each other.

View file

@ -163,6 +163,19 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return *this;
}
template<typename TilMath>
point scale(TilMath, const float scale) const
{
struct
{
float x, y;
} pt;
THROW_HR_IF(E_ABORT, !base::CheckMul(scale, _x).AssignIfValid(&pt.x));
THROW_HR_IF(E_ABORT, !base::CheckMul(scale, _y).AssignIfValid(&pt.y));
return til::point(TilMath(), pt);
}
point operator/(const point& other) const
{
ptrdiff_t x;

View file

@ -3,10 +3,6 @@
#pragma once
#include "point.h"
#include "size.h"
#include "some.h"
#ifdef UNIT_TESTING
class RectangleTests;
#endif
@ -179,6 +175,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
// This template will convert to rectangle from anything that has a Left, Top, Right, and Bottom field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr rectangle(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().Left)> && std::is_floating_point_v<decltype(std::declval<TOther>().Top)> && std::is_floating_point_v<decltype(std::declval<TOther>().Right)> && std::is_floating_point_v<decltype(std::declval<TOther>().Bottom)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ TilMath::template cast<ptrdiff_t>(other.Left), TilMath::template cast<ptrdiff_t>(other.Top) }, til::point{ TilMath::template cast<ptrdiff_t>(other.Right), TilMath::template cast<ptrdiff_t>(other.Bottom) })
{
}
// This template will convert to rectangle from anything that has a left, top, right, and bottom field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr rectangle(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().left)> && std::is_floating_point_v<decltype(std::declval<TOther>().top)> && std::is_floating_point_v<decltype(std::declval<TOther>().right)> && std::is_floating_point_v<decltype(std::declval<TOther>().bottom)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ TilMath::template cast<ptrdiff_t>(other.left), TilMath::template cast<ptrdiff_t>(other.top) }, til::point{ TilMath::template cast<ptrdiff_t>(other.right), TilMath::template cast<ptrdiff_t>(other.bottom) })
{
}
constexpr bool operator==(const rectangle& other) const noexcept
{
return _topLeft == other._topLeft &&
@ -636,6 +648,38 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return *this;
}
// scale_up will scale the entire rectangle up by the size factor
// This includes moving the origin.
rectangle scale_up(const size& size) const
{
const auto topLeft = _topLeft * size;
const auto bottomRight = _bottomRight * size;
return til::rectangle{ topLeft, bottomRight };
}
// scale_down will scale the entire rectangle down by the size factor,
// but rounds the bottom-right corner out.
// This includes moving the origin.
rectangle scale_down(const size& size) const
{
auto topLeft = _topLeft;
auto bottomRight = _bottomRight;
topLeft = topLeft / size;
// Move bottom right point into a size
// Use size specialization of divide_ceil to round up against the size given.
// Add leading addition to point to convert it back into a point.
bottomRight = til::point{} + til::size{ right(), bottom() }.divide_ceil(size);
return til::rectangle{ topLeft, bottomRight };
}
template<typename TilMath>
rectangle scale(TilMath, const float scale) const
{
return til::rectangle{ _topLeft.scale(TilMath{}, scale), _bottomRight.scale(TilMath{}, scale) };
}
#pragma endregion
constexpr ptrdiff_t top() const noexcept

View file

@ -126,6 +126,19 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return size{ width, height };
}
template<typename TilMath>
size scale(TilMath, const float scale) const
{
struct
{
float Width, Height;
} sz;
THROW_HR_IF(E_ABORT, !base::CheckMul(scale, _width).AssignIfValid(&sz.Width));
THROW_HR_IF(E_ABORT, !base::CheckMul(scale, _height).AssignIfValid(&sz.Height));
return til::size(TilMath(), sz);
}
size operator/(const size& other) const
{
ptrdiff_t width;

View file

@ -303,6 +303,22 @@ void Renderer::TriggerSelection()
// Get selection rectangles
const auto rects = _GetSelectionRects();
// Restrict all previous selection rectangles to inside the current viewport bounds
for (auto& sr : _previousSelection)
{
// Make the exclusive SMALL_RECT into a til::rectangle.
til::rectangle rc{ Viewport::FromExclusive(sr).ToInclusive() };
// Make a viewport representing the coordinates that are currently presentable.
const til::rectangle viewport{ til::size{ _pData->GetViewport().Dimensions() } };
// Intersect them so we only invalidate things that are still visible.
rc &= viewport;
// Convert back into the exclusive SMALL_RECT and store in the vector.
sr = Viewport::FromInclusive(rc).ToExclusive();
}
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection));
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
@ -330,13 +346,26 @@ bool Renderer::_CheckViewportAndScroll()
coordDelta.X = srOldViewport.Left - srNewViewport.Left;
coordDelta.Y = srOldViewport.Top - srNewViewport.Top;
std::for_each(_rgpEngines.begin(), _rgpEngines.end(), [&](IRenderEngine* const pEngine) {
LOG_IF_FAILED(pEngine->UpdateViewport(srNewViewport));
LOG_IF_FAILED(pEngine->InvalidateScroll(&coordDelta));
});
for (auto engine : _rgpEngines)
{
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
}
_srViewportPrevious = srNewViewport;
return coordDelta.X != 0 || coordDelta.Y != 0;
if (coordDelta.X != 0 || coordDelta.Y != 0)
{
for (auto engine : _rgpEngines)
{
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
}
_ScrollPreviousSelection(coordDelta);
return true;
}
return false;
}
// Routine Description:
@ -369,6 +398,8 @@ void Renderer::TriggerScroll(const COORD* const pcoordDelta)
LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta));
});
_ScrollPreviousSelection(*pcoordDelta);
_NotifyPaintFrame();
}
@ -927,10 +958,13 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
{
for (auto dirtyRect : dirtyAreas)
{
// Make a copy as `TrimToViewport` will manipulate it and
// can destroy it for the next dirtyRect to test against.
auto rectCopy = rect;
Viewport dirtyView = Viewport::FromInclusive(dirtyRect);
if (dirtyView.TrimToViewport(&rect))
if (dirtyView.TrimToViewport(&rectCopy))
{
LOG_IF_FAILED(pEngine->PaintSelection(rect));
LOG_IF_FAILED(pEngine->PaintSelection(rectCopy));
}
}
}
@ -1002,6 +1036,33 @@ std::vector<SMALL_RECT> Renderer::_GetSelectionRects() const
return result;
}
// Method Description:
// - Offsets all of the selection rectangles we might be holding onto
// as the previously selected area. If the whole viewport scrolls,
// we need to scroll these areas also to ensure they're invalidated
// properly when the selection further changes.
// Arguments:
// - delta - The scroll delta
// Return Value:
// - <none> - Updates internal state instead.
void Renderer::_ScrollPreviousSelection(const til::point delta)
{
if (delta != til::point{ 0, 0 })
{
for (auto& sr : _previousSelection)
{
// Get a rectangle representing this piece of the selection.
til::rectangle rc = Viewport::FromExclusive(sr).ToInclusive();
// Offset the entire existing rectangle by the delta.
rc += delta;
// Store it back into the vector.
sr = Viewport::FromInclusive(rc).ToExclusive();
}
}
}
// Method Description:
// - Adds another Render engine to this renderer. Future rendering calls will
// also be sent to the new renderer.

View file

@ -119,6 +119,7 @@ namespace Microsoft::Console::Render
SMALL_RECT _srViewportPrevious;
std::vector<SMALL_RECT> _GetSelectionRects() const;
void _ScrollPreviousSelection(const til::point delta);
std::vector<SMALL_RECT> _previousSelection;
[[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine);

File diff suppressed because it is too large Load diff

View file

@ -121,7 +121,7 @@ namespace Microsoft::Console::Render
SwapChainMode _chainMode;
HWND _hwndTarget;
SIZE _sizeTarget;
til::size _sizeTarget;
int _dpi;
float _scale;
@ -130,8 +130,8 @@ namespace Microsoft::Console::Render
bool _isEnabled;
bool _isPainting;
SIZE _displaySizePixels;
SIZE _glyphCell;
til::size _displaySizePixels;
til::size _glyphCell;
D2D1_COLOR_F _defaultForegroundColor;
D2D1_COLOR_F _defaultBackgroundColor;
@ -140,19 +140,13 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
bool _isInvalidUsed;
RECT _invalidRect;
SIZE _invalidScroll;
void _InvalidOr(SMALL_RECT sr) noexcept;
void _InvalidOr(RECT rc) noexcept;
void _InvalidOffset(POINT pt);
bool _firstFrame;
bool _invalidateFullRows;
til::bitmap _invalidMap;
til::point _invalidScroll;
bool _presentReady;
RECT _presentDirty;
std::vector<RECT> _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
@ -244,9 +238,9 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace) const noexcept;
[[nodiscard]] COORD _GetFontSize() const noexcept;
[[nodiscard]] til::size _GetClientSize() const;
[[nodiscard]] SIZE _GetClientSize() const noexcept;
void _InvalidateRectangle(const til::rectangle& rc);
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;

View file

@ -4,9 +4,11 @@
#pragma once
// This includes support libraries from the CRT, STL, WIL, and GSL
#define BLOCK_TIL // We want to include it later, after DX.
#include "LibraryIncludes.h"
#include <windows.h>
#include <winmeta.h>
#include "..\host\conddkrefs.h"
#include <condrv.h>
@ -34,4 +36,7 @@
#include <dwrite_2.h>
#include <dwrite_3.h>
// Re-include TIL at the bottom to gain DX superpowers.
#include "til.h"
#pragma hdrstop

View file

@ -844,6 +844,14 @@ class BitmapTests
VERIFY_IS_FALSE(bitmap.all());
}
TEST_METHOD(Size)
{
til::size sz{ 5, 10 };
til::bitmap map{ sz };
VERIFY_ARE_EQUAL(sz, map.size());
}
TEST_METHOD(Runs)
{
// This map --> Those runs

View file

@ -417,6 +417,33 @@ class PointTests
}
}
TEST_METHOD(ScaleByFloat)
{
Log::Comment(L"0.) Scale that should be in bounds.");
{
const til::point pt{ 5, 10 };
const float scale = 1.783f;
const til::point expected{ static_cast<ptrdiff_t>(ceil(5 * scale)), static_cast<ptrdiff_t>(ceil(10 * scale)) };
const auto actual = pt.scale(til::math::ceiling, scale);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Scale results in value that is too large.");
{
const til::point pt{ 5, 10 };
constexpr float scale = std::numeric_limits<float>().max();
auto fn = [&]() {
pt.scale(til::math::ceiling, scale);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Division)
{
Log::Comment(L"0.) Division of two things that should be in bounds.");

View file

@ -797,6 +797,79 @@ class RectangleTests
}
}
TEST_METHOD(ScaleUpSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Multiply by size to scale from cells to pixels");
{
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10 * 3, 20 * 7, 30 * 3, 40 * 7 };
const auto actual = start.scale_up(scale);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Multiply by size with width way too big.");
{
const til::size scale{ std::numeric_limits<ptrdiff_t>().max(), static_cast<ptrdiff_t>(7) };
auto fn = [&]() {
const auto actual = start.scale_up(scale);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"3.) Multiply by size with height way too big.");
{
const til::size scale{ static_cast<ptrdiff_t>(3), std::numeric_limits<ptrdiff_t>().max() };
auto fn = [&]() {
const auto actual = start.scale_up(scale);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(ScaleDownSize)
{
const til::rectangle start{ 10, 20, 29, 40 };
Log::Comment(L"0.) Division by size to scale from pixels to cells");
{
const til::size scale{ 3, 7 };
// Division is special. The top and left round down.
// The bottom and right round up. This is to ensure that the cells
// the smaller rectangle represents fully cover all the pixels
// of the larger rectangle.
// L: 10 / 3 = 3.333 --> round down --> 3
// T: 20 / 7 = 2.857 --> round down --> 2
// R: 29 / 3 = 9.667 --> round up ----> 10
// B: 40 / 7 = 5.714 --> round up ----> 6
const til::rectangle expected{ 3, 2, 10, 6 };
const auto actual = start.scale_down(scale);
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(ScaleByFloat)
{
const til::rectangle start{ 10, 20, 30, 40 };
const float scale = 1.45f;
// This is not a test of the various TilMath rounding methods
// so we're only checking one here.
// Expected here is written based on the "ceiling" outcome.
const til::rectangle expected{ 15, 29, 44, 58 };
const auto actual = start.scale(til::math::ceiling, scale);
VERIFY_ARE_EQUAL(actual, expected);
}
TEST_METHOD(Top)
{
const til::rectangle rc{ 5, 10, 15, 20 };
@ -1361,4 +1434,101 @@ class RectangleTests
}
#pragma endregion
template<typename T>
struct RectangleTypeWithLowercase
{
T left, top, right, bottom;
};
template<typename T>
struct RectangleTypeWithCapitalization
{
T Left, Top, Right, Bottom;
};
TEST_METHOD(CastFromFloatWithMathTypes)
{
RectangleTypeWithLowercase<float> lowerFloatIntegral{ 1.f, 2.f, 3.f, 4.f };
RectangleTypeWithLowercase<float> lowerFloat{ 1.6f, 2.4f, 3.2f, 4.8f };
RectangleTypeWithCapitalization<double> capitalDoubleIntegral{ 3., 4., 5., 6. };
RectangleTypeWithCapitalization<double> capitalDouble{ 3.6, 4.4, 5.7, 6.3 };
Log::Comment(L"0.) Ceiling");
{
{
til::rectangle converted{ til::math::ceiling, lowerFloatIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::ceiling, lowerFloat };
VERIFY_ARE_EQUAL((til::rectangle{ 2, 3, 4, 5 }), converted);
}
{
til::rectangle converted{ til::math::ceiling, capitalDoubleIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
{
til::rectangle converted{ til::math::ceiling, capitalDouble };
VERIFY_ARE_EQUAL((til::rectangle{ 4, 5, 6, 7 }), converted);
}
}
Log::Comment(L"1.) Flooring");
{
{
til::rectangle converted{ til::math::flooring, lowerFloatIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::flooring, lowerFloat };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::flooring, capitalDoubleIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
{
til::rectangle converted{ til::math::flooring, capitalDouble };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
}
Log::Comment(L"2.) Rounding");
{
{
til::rectangle converted{ til::math::rounding, lowerFloatIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::rounding, lowerFloat };
VERIFY_ARE_EQUAL((til::rectangle{ 2, 2, 3, 5 }), converted);
}
{
til::rectangle converted{ til::math::rounding, capitalDoubleIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
{
til::rectangle converted{ til::math::rounding, capitalDouble };
VERIFY_ARE_EQUAL((til::rectangle{ 4, 4, 6, 6 }), converted);
}
}
Log::Comment(L"3.) Truncating");
{
{
til::rectangle converted{ til::math::truncating, lowerFloatIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::truncating, lowerFloat };
VERIFY_ARE_EQUAL((til::rectangle{ 1, 2, 3, 4 }), converted);
}
{
til::rectangle converted{ til::math::truncating, capitalDoubleIntegral };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
{
til::rectangle converted{ til::math::truncating, capitalDouble };
VERIFY_ARE_EQUAL((til::rectangle{ 3, 4, 5, 6 }), converted);
}
}
}
};

View file

@ -309,6 +309,33 @@ class SizeTests
}
}
TEST_METHOD(ScaleByFloat)
{
Log::Comment(L"0.) Scale that should be in bounds.");
{
const til::size sz{ 5, 10 };
const float scale = 1.783f;
const til::size expected{ static_cast<ptrdiff_t>(ceil(5 * scale)), static_cast<ptrdiff_t>(ceil(10 * scale)) };
const auto actual = sz.scale(til::math::ceiling, scale);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Scale results in value that is too large.");
{
const til::size sz{ 5, 10 };
constexpr float scale = std::numeric_limits<float>().max();
auto fn = [&]() {
sz.scale(til::math::ceiling, scale);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Division)
{
Log::Comment(L"0.) Division of two things that should be in bounds.");