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:
parent
ffbdfe32ac
commit
79684bf821
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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.");
|
||||
|
|
Loading…
Reference in a new issue