forked from microsoft/terminal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrenderer.cpp
1406 lines (1229 loc) · 51.7 KB
/
renderer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "renderer.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
static constexpr auto maxRetriesForRenderEngine = 3;
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
#define FOREACH_ENGINE(var) \
for (auto var : _engines) \
if (!var) \
break; \
else
// Routine Description:
// - Creates a new renderer controller for a console.
// Arguments:
// - pData - The interface to console data structures required for rendering
// - pEngine - The output engine for targeting each rendering frame
// Return Value:
// - An instance of a Renderer.
Renderer::Renderer(const RenderSettings& renderSettings,
IRenderData* pData,
_In_reads_(cEngines) IRenderEngine** const rgpEngines,
const size_t cEngines,
std::unique_ptr<RenderThread> thread) :
_renderSettings(renderSettings),
_pData(pData),
_pThread{ std::move(thread) }
{
for (size_t i = 0; i < cEngines; i++)
{
AddRenderEngine(rgpEngines[i]);
}
}
// Routine Description:
// - Destroys an instance of a renderer
// Arguments:
// - <none>
// Return Value:
// - <none>
Renderer::~Renderer()
{
// RenderThread blocks until it has shut down.
_destructing = true;
_pThread.reset();
}
// Routine Description:
// - Walks through the console data structures to compose a new frame based on the data that has changed since last call and outputs it to the connected rendering engine.
// Arguments:
// - <none>
// Return Value:
// - HRESULT S_OK, GDI error, Safe Math error, or state/argument errors.
[[nodiscard]] HRESULT Renderer::PaintFrame()
{
FOREACH_ENGINE(pEngine)
{
auto tries = maxRetriesForRenderEngine;
while (tries > 0)
{
if (_destructing)
{
return S_FALSE;
}
const auto hr = _PaintFrameForEngine(pEngine);
if (SUCCEEDED(hr))
{
break;
}
LOG_HR_IF(hr, hr != E_PENDING);
if (--tries == 0)
{
// Stop trying.
_pThread->DisablePainting();
if (_pfnRendererEnteredErrorState)
{
_pfnRendererEnteredErrorState();
}
// If there's no callback, we still don't want to FAIL_FAST: the renderer going black
// isn't near as bad as the entire application aborting. We're a component. We shouldn't
// abort applications that host us.
return S_FALSE;
}
// Add a bit of backoff.
// Sleep 150ms, 300ms, 450ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (maxRetriesForRenderEngine - tries));
}
}
return S_OK;
}
[[nodiscard]] HRESULT Renderer::_PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept
try
{
FAIL_FAST_IF_NULL(pEngine); // This is a programming error. Fail fast.
_pData->LockConsole();
auto unlock = wil::scope_exit([&]() {
_pData->UnlockConsole();
});
// Last chance check if anything scrolled without an explicit invalidate notification since the last frame.
_CheckViewportAndScroll();
// Try to start painting a frame
const auto hr = pEngine->StartPaint();
RETURN_IF_FAILED(hr);
// Return early if there's nothing to paint.
// The renderer itself tracks if there's something to do with the title, the
// engine won't know that.
if (S_FALSE == hr)
{
return S_OK;
}
auto endPaint = wil::scope_exit([&]() {
LOG_IF_FAILED(pEngine->EndPaint());
// If the engine tells us it really wants to redraw immediately,
// tell the thread so it doesn't go to sleep and ticks again
// at the next opportunity.
if (pEngine->RequiresContinuousRedraw())
{
NotifyPaintFrame();
}
});
// A. Prep Colors
RETURN_IF_FAILED(_UpdateDrawingBrushes(pEngine, {}, false, true));
// B. Perform Scroll Operations
RETURN_IF_FAILED(_PerformScrolling(pEngine));
// C. Prepare the engine with additional information before we start drawing.
RETURN_IF_FAILED(_PrepareRenderInfo(pEngine));
// 1. Paint Background
RETURN_IF_FAILED(_PaintBackground(pEngine));
// 2. Paint Rows of Text
_PaintBufferOutput(pEngine);
// 3. Paint overlays that reside above the text buffer
_PaintOverlays(pEngine);
// 4. Paint Selection
_PaintSelection(pEngine);
// 5. Paint Cursor
_PaintCursor(pEngine);
// 6. Paint window title
RETURN_IF_FAILED(_PaintTitle(pEngine));
// Force scope exit end paint to finish up collecting information and possibly painting
endPaint.reset();
// Force scope exit unlock to let go of global lock so other threads can run
unlock.reset();
// Trigger out-of-lock presentation for renderers that can support it
RETURN_IF_FAILED(pEngine->Present());
// As we leave the scope, EndPaint will be called (declared above)
return S_OK;
}
CATCH_RETURN()
void Renderer::NotifyPaintFrame() noexcept
{
// If we're running in the unittests, we might not have a render thread.
if (_pThread)
{
// The thread will provide throttling for us.
_pThread->NotifyPaint();
}
}
// Routine Description:
// - Called when the system has requested we redraw a portion of the console.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient)
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient));
}
NotifyPaintFrame();
}
// Routine Description:
// - Called when a particular region within the console buffer has changed.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerRedraw(const Viewport& region)
{
auto view = _viewport;
auto srUpdateRegion = region.ToExclusive();
// If the dirty region has double width lines, we need to double the size of
// the right margin to make sure all the affected cells are invalidated.
const auto& buffer = _pData->GetTextBuffer();
for (auto row = srUpdateRegion.top; row < srUpdateRegion.bottom; row++)
{
if (buffer.IsDoubleWidthLine(row))
{
srUpdateRegion.right *= 2;
break;
}
}
if (view.TrimToViewport(&srUpdateRegion))
{
view.ConvertToOrigin(&srUpdateRegion);
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion));
}
NotifyPaintFrame();
}
}
// Routine Description:
// - Called when a particular coordinate within the console buffer has changed.
// Arguments:
// - pcoord: The buffer-space coordinate that has changed.
// Return Value:
// - <none>
void Renderer::TriggerRedraw(const til::point* const pcoord)
{
TriggerRedraw(Viewport::FromCoord(*pcoord)); // this will notify to paint if we need it.
}
// Routine Description:
// - Called when the cursor has moved in the buffer. Allows for RenderEngines to
// differentiate between cursor movements and other invalidates.
// Visual Renderers (ex GDI) should invalidate the position, while the VT
// engine ignores this. See MSFT:14711161.
// Arguments:
// - pcoord: The buffer-space position of the cursor.
// Return Value:
// - <none>
void Renderer::TriggerRedrawCursor(const til::point* const pcoord)
{
// We first need to make sure the cursor position is within the buffer,
// otherwise testing for a double width character can throw an exception.
const auto& buffer = _pData->GetTextBuffer();
if (buffer.GetSize().IsInBounds(*pcoord))
{
// We then calculate the region covered by the cursor. This requires
// converting the buffer coordinates to an equivalent range of screen
// cells for the cursor, taking line rendition into account.
const auto lineRendition = buffer.GetLineRendition(pcoord->y);
const auto cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
til::inclusive_rect cursorRect = { pcoord->x, pcoord->y, pcoord->x + cursorWidth - 1, pcoord->y };
cursorRect = BufferToScreenLine(cursorRect, lineRendition);
// That region is then clipped within the viewport boundaries and we
// only trigger a redraw if the resulting region is not empty.
auto view = _pData->GetViewport();
auto updateRect = til::rect{ cursorRect };
if (view.TrimToViewport(&updateRect))
{
view.ConvertToOrigin(&updateRect);
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateRect));
}
NotifyPaintFrame();
}
}
}
// Routine Description:
// - Called when something that changes the output state has occurred and the entire frame is now potentially invalid.
// - NOTE: Use sparingly. Try to reduce the refresh region where possible. Only use when a global state change has occurred.
// Arguments:
// - backgroundChanged - Set to true if the background color has changed.
// - frameChanged - Set to true if the frame colors have changed.
// Return Value:
// - <none>
void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameChanged)
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateAll());
}
NotifyPaintFrame();
if (backgroundChanged && _pfnBackgroundColorChanged)
{
_pfnBackgroundColorChanged();
}
if (frameChanged && _pfnFrameColorChanged)
{
_pfnFrameColorChanged();
}
}
// Method Description:
// - Called when the host is about to die, to give the renderer one last chance
// to paint before the host exits.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerTeardown() noexcept
{
// We need to shut down the paint thread on teardown.
_pThread->WaitForPaintCompletionAndDisable(INFINITE);
// Then walk through and do one final paint on the caller's thread.
FOREACH_ENGINE(pEngine)
{
auto fEngineRequestsRepaint = false;
auto hr = pEngine->PrepareForTeardown(&fEngineRequestsRepaint);
LOG_IF_FAILED(hr);
if (SUCCEEDED(hr) && fEngineRequestsRepaint)
{
LOG_IF_FAILED(_PaintFrameForEngine(pEngine));
}
}
}
// Routine Description:
// - Called when the selected area in the console has changed.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerSelection()
{
try
{
// Get selection rectangles
auto rects = _GetSelectionRects();
// Make a viewport representing the coordinates that are currently presentable.
const til::rect viewport{ _pData->GetViewport().Dimensions() };
// Restrict all previous selection rectangles to inside the current viewport bounds
for (auto& sr : _previousSelection)
{
sr &= viewport;
}
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection));
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
}
_previousSelection = std::move(rects);
NotifyPaintFrame();
}
CATCH_LOG();
}
// Routine Description:
// - Called when we want to check if the viewport has moved and scroll accordingly if so.
// Arguments:
// - <none>
// Return Value:
// - True if something changed and we scrolled. False otherwise.
bool Renderer::_CheckViewportAndScroll()
{
const auto srOldViewport = _viewport.ToInclusive();
const auto srNewViewport = _pData->GetViewport().ToInclusive();
if (!_forceUpdateViewport && srOldViewport == srNewViewport)
{
return false;
}
_viewport = Viewport::FromInclusive(srNewViewport);
_forceUpdateViewport = false;
til::point coordDelta;
coordDelta.x = srOldViewport.left - srNewViewport.left;
coordDelta.y = srOldViewport.top - srNewViewport.top;
FOREACH_ENGINE(engine)
{
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
}
_ScrollPreviousSelection(coordDelta);
return true;
}
// Routine Description:
// - Called when a scroll operation has occurred by manipulating the viewport.
// - This is a special case as calling out scrolls explicitly drastically improves performance.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerScroll()
{
if (_CheckViewportAndScroll())
{
NotifyPaintFrame();
}
}
// Routine Description:
// - Called when a scroll operation wishes to explicitly adjust the frame by the given coordinate distance.
// - This is a special case as calling out scrolls explicitly drastically improves performance.
// - This should only be used when the viewport is not modified. It lets us know we can "scroll anyway" to save perf,
// because the backing circular buffer rotated out from behind the viewport.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerScroll(const til::point* const pcoordDelta)
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta));
}
_ScrollPreviousSelection(*pcoordDelta);
NotifyPaintFrame();
}
// Routine Description:
// - Called when the text buffer is about to circle its backing buffer.
// A renderer might want to get painted before that happens.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerFlush(const bool circling)
{
const auto rects = _GetSelectionRects();
FOREACH_ENGINE(pEngine)
{
auto fEngineRequestsRepaint = false;
auto hr = pEngine->InvalidateFlush(circling, &fEngineRequestsRepaint);
LOG_IF_FAILED(hr);
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
if (SUCCEEDED(hr) && fEngineRequestsRepaint)
{
LOG_IF_FAILED(_PaintFrameForEngine(pEngine));
}
}
}
// Routine Description:
// - Called when the title of the console window has changed. Indicates that we
// should update the title on the next frame.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerTitleChange()
{
const auto newTitle = _pData->GetConsoleTitle();
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle));
}
NotifyPaintFrame();
}
void Renderer::TriggerNewTextNotification(const std::wstring_view newText)
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->NotifyNewText(newText));
}
}
// Routine Description:
// - Update the title for a particular engine.
// Arguments:
// - pEngine: the engine to update the title for.
// Return Value:
// - the HRESULT of the underlying engine's UpdateTitle call.
HRESULT Renderer::_PaintTitle(IRenderEngine* const pEngine)
{
const auto newTitle = _pData->GetConsoleTitle();
return pEngine->UpdateTitle(newTitle);
}
// Routine Description:
// - Called when a change in font or DPI has been detected.
// Arguments:
// - iDpi - New DPI value
// - FontInfoDesired - A description of the font we would like to have.
// - FontInfo - Data that will be fixed up/filled on return with the chosen font data.
// Return Value:
// - <none>
void Renderer::TriggerFontChange(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo)
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->UpdateDpi(iDpi));
LOG_IF_FAILED(pEngine->UpdateFont(FontInfoDesired, FontInfo));
}
NotifyPaintFrame();
}
// Routine Description:
// - Called when the active soft font has been updated.
// Arguments:
// - bitPattern - An array of scanlines representing all the glyphs in the font.
// - cellSize - The cell size for an individual glyph.
// - centeringHint - The horizontal extent that glyphs are offset from center.
// Return Value:
// - <none>
void Renderer::UpdateSoftFont(const std::span<const uint16_t> bitPattern, const til::size cellSize, const size_t centeringHint)
{
// We reserve PUA code points U+EF20 to U+EF7F for soft fonts, but the range
// that we test for in _IsSoftFontChar will depend on the size of the active
// bitPattern. If it's empty (i.e. no soft font is set), then nothing will
// match, and those code points will be treated the same as everything else.
const auto softFontCharCount = cellSize.height ? bitPattern.size() / cellSize.height : 0;
_lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1;
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->UpdateSoftFont(bitPattern, cellSize, centeringHint));
}
TriggerRedrawAll();
}
// We initially tried to have a "_isSoftFontChar" member function, but MSVC
// failed to inline it at _all_ call sites (check invocations inside loops).
// This issue strangely doesn't occur with static functions.
bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar)
{
return v.size() == 1 && v[0] >= firstSoftFontChar && v[0] <= lastSoftFontChar;
}
// Routine Description:
// - Get the information on what font we would be using if we decided to create a font with the given parameters
// - This is for use with speculative calculations.
// Arguments:
// - iDpi - The DPI of the target display
// - pFontInfoDesired - A description of the font we would like to have.
// - pFontInfo - Data that will be fixed up/filled on return with the chosen font data.
// Return Value:
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT Renderer::GetProposedFont(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo)
{
// There will only every really be two engines - the real head and the VT
// renderer. We won't know which is which, so iterate over them.
// Only return the result of the successful one if it's not S_FALSE (which is the VT renderer)
// TODO: 14560740 - The Window might be able to get at this info in a more sane manner
FOREACH_ENGINE(pEngine)
{
const auto hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi));
// We're looking for specifically S_OK, S_FALSE is not good enough.
if (hr == S_OK)
{
return hr;
}
}
return E_FAIL;
}
// Routine Description:
// - Tests against the current rendering engine to see if this particular character would be considered
// full-width (inscribed in a square, twice as wide as a standard Western character, typically used for CJK
// languages) or half-width.
// - Typically used to determine how many positions in the backing buffer a particular character should fill.
// NOTE: This only handles 1 or 2 wide (in monospace terms) characters.
// Arguments:
// - glyph - the utf16 encoded codepoint to test
// Return Value:
// - True if the codepoint is full-width (two wide), false if it is half-width (one wide).
bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph)
{
auto fIsFullWidth = false;
// There will only every really be two engines - the real head and the VT
// renderer. We won't know which is which, so iterate over them.
// Only return the result of the successful one if it's not S_FALSE (which is the VT renderer)
// TODO: 14560740 - The Window might be able to get at this info in a more sane manner
FOREACH_ENGINE(pEngine)
{
const auto hr = LOG_IF_FAILED(pEngine->IsGlyphWideByFont(glyph, &fIsFullWidth));
// We're looking for specifically S_OK, S_FALSE is not good enough.
if (hr == S_OK)
{
break;
}
}
return fIsFullWidth;
}
// Routine Description:
// - Sets an event in the render thread that allows it to proceed, thus enabling painting.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::EnablePainting()
{
// When the renderer is constructed, the initial viewport won't be available yet,
// but once EnablePainting is called it should be safe to retrieve.
_viewport = _pData->GetViewport();
_forceUpdateViewport = true;
// When running the unit tests, we may be using a render without a render thread.
if (_pThread)
{
_pThread->EnablePainting();
}
}
// Routine Description:
// - Waits for the current paint operation to complete, if any, up to the specified timeout.
// - Resets an event in the render thread that precludes it from advancing, thus disabling rendering.
// - If no paint operation is currently underway, returns immediately.
// Arguments:
// - dwTimeoutMs - Milliseconds to wait for the current paint operation to complete, if any (can be INFINITE).
// Return Value:
// - <none>
void Renderer::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs)
{
_pThread->WaitForPaintCompletionAndDisable(dwTimeoutMs);
}
// Routine Description:
// - Paint helper to fill in the background color of the invalid area within the frame.
// Arguments:
// - <none>
// Return Value:
// - <none>
[[nodiscard]] HRESULT Renderer::_PaintBackground(_In_ IRenderEngine* const pEngine)
{
return pEngine->PaintBackground();
}
// Routine Description:
// - Paint helper to copy the primary console buffer text onto the screen.
// - This portion primarily handles figuring the current viewport, comparing it/trimming it versus the invalid portion of the frame, and queuing up, row by row, which pieces of text need to be further processed.
// - See also: Helper functions that separate out each complexity of text rendering.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
{
// This is the subsection of the entire screen buffer that is currently being presented.
// It can move left/right or top/bottom depending on how the viewport is scrolled
// relative to the entire buffer.
const auto view = _pData->GetViewport();
// This is effectively the number of cells on the visible screen that need to be redrawn.
// The origin is always 0, 0 because it represents the screen itself, not the underlying buffer.
std::span<const til::rect> dirtyAreas;
LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas));
// This is to make sure any transforms are reset when this paint is finished.
auto resetLineTransform = wil::scope_exit([&]() {
LOG_IF_FAILED(pEngine->ResetLineTransform());
});
for (const auto& dirtyRect : dirtyAreas)
{
if (!dirtyRect)
{
continue;
}
auto dirty = Viewport::FromExclusive(dirtyRect);
// Shift the origin of the dirty region to match the underlying buffer so we can
// compare the two regions directly for intersection.
dirty = Viewport::Offset(dirty, view.Origin());
// The intersection between what is dirty on the screen (in need of repaint)
// and what is supposed to be visible on the screen (the viewport) is what
// we need to walk through line-by-line and repaint onto the screen.
const auto redraw = Viewport::Intersect(dirty, view);
// Retrieve the text buffer so we can read information out of it.
const auto& buffer = _pData->GetTextBuffer();
// Now walk through each row of text that we need to redraw.
for (auto row = redraw.Top(); row < redraw.BottomExclusive(); row++)
{
// Calculate the boundaries of a single line. This is from the left to right edge of the dirty
// area in width and exactly 1 tall.
const auto screenLine = til::inclusive_rect{ redraw.Left(), row, redraw.RightInclusive(), row };
// Convert the screen coordinates of the line to an equivalent
// range of buffer cells, taking line rendition into account.
const auto lineRendition = buffer.GetLineRendition(row);
const auto bufferLine = Viewport::FromInclusive(ScreenToBufferLine(screenLine, lineRendition));
// Find where on the screen we should place this line information. This requires us to re-map
// the buffer-based origin of the line back onto the screen-based origin of the line.
// For example, the screen might say we need to paint line 1 because it is dirty but the viewport
// is actually looking at line 26 relative to the buffer. This means that we need line 27 out
// of the backing buffer to fill in line 1 of the screen.
const auto screenPosition = bufferLine.Origin() - til::point{ 0, view.Top() };
// Retrieve the cell information iterator limited to just this line we want to redraw.
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
// Calculate if two things are true:
// 1. this row wrapped
// 2. We're painting the last col of the row.
// In that case, set lineWrapped=true for the _PaintBufferOutputHelper call.
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().y).WasWrapForced()) &&
(bufferLine.RightExclusive() == buffer.GetSize().Width());
// Prepare the appropriate line transform for the current row and viewport offset.
LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.y, view.Left()));
// Ask the helper to paint through this specific line.
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
}
}
}
static bool _IsAllSpaces(const std::wstring_view v)
{
// first non-space char is not found (is npos)
return v.find_first_not_of(L' ') == decltype(v)::npos;
}
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
TextBufferCellIterator it,
const til::point target,
const bool lineWrapped)
{
auto globalInvert{ _renderSettings.GetRenderMode(RenderSettings::Mode::ScreenReversed) };
// If we have valid data, let's figure out how to draw it.
if (it)
{
// TODO: MSFT: 20961091 - This is a perf issue. Instead of rebuilding this and allocing memory to hold the reinterpretation,
// we should have an iterator/view adapter for the rendering.
// That would probably also eliminate the RenderData needing to give us the entire TextBuffer as well...
// Retrieve the iterator for one line of information.
til::CoordType cols = 0;
// Retrieve the first color.
auto color = it->TextAttr();
// Retrieve the first pattern id
auto patternIds = _pData->GetPatternId(target);
// Determine whether we're using a soft font.
auto usingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar);
// And hold the point where we should start drawing.
auto screenPoint = target;
// This outer loop will continue until we reach the end of the text we are trying to draw.
while (it)
{
// Hold onto the current run color right here for the length of the outer loop.
// We'll be changing the persistent one as we run through the inner loops to detect
// when a run changes, but we will still need to know this color at the bottom
// when we go to draw gridlines for the length of the run.
const auto currentRunColor = color;
// Hold onto the current pattern id as well
const auto currentPatternId = patternIds;
// Update the drawing brushes with our color and font usage.
THROW_IF_FAILED(_UpdateDrawingBrushes(pEngine, currentRunColor, usingSoftFont, false));
// Advance the point by however many columns we've just outputted and reset the accumulator.
screenPoint.x += cols;
cols = 0;
// Hold onto the start of this run iterator and the target location where we started
// in case we need to do some special work to paint the line drawing characters.
const auto currentRunItStart = it;
const auto currentRunTargetStart = screenPoint;
// Ensure that our cluster vector is clear.
_clusterBuffer.clear();
// Reset our flag to know when we're in the special circumstance
// of attempting to draw only the right-half of a two-column character
// as the first item in our run.
auto trimLeft = false;
// Run contains wide character (>1 columns)
auto containsWideCharacter = false;
// This inner loop will accumulate clusters until the color changes.
// When the color changes, it will save the new color off and break.
// We also accumulate clusters according to regex patterns
do
{
til::point thisPoint{ screenPoint.x + cols, screenPoint.y };
const auto thisPointPatterns = _pData->GetPatternId(thisPoint);
const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar);
const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont;
if (color != it->TextAttr() || changedPatternOrFont)
{
auto newAttr{ it->TextAttr() };
// foreground doesn't matter for runs of spaces (!)
// if we trick it . . . we call Paint far fewer times for cmatrix
if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont)
{
color = newAttr;
patternIds = thisPointPatterns;
usingSoftFont = thisUsingSoftFont;
break; // vend this run
}
}
// Walk through the text data and turn it into rendering clusters.
// Keep the columnCount as we go to improve performance over digging it out of the vector at the end.
auto columnCount = it->Columns();
// If we're on the first cluster to be added and it's marked as "trailing"
// (a.k.a. the right half of a two column character), then we need some special handling.
if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing)
{
// Move left to the one so the whole character can be struck correctly.
--screenPoint.x;
// And tell the next function to trim off the left half of it.
trimLeft = true;
// And add one to the number of columns we expect it to take as we insert it.
++columnCount;
}
if (columnCount > 1)
{
containsWideCharacter = true;
}
// Advance the cluster and column counts.
_clusterBuffer.emplace_back(it->Chars(), columnCount);
it += std::max(it->Columns(), 1); // prevent infinite loop for no visible columns
cols += columnCount;
} while (it);
// Do the painting.
THROW_IF_FAILED(pEngine->PaintBufferLine({ _clusterBuffer.data(), _clusterBuffer.size() }, screenPoint, trimLeft, lineWrapped));
// If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data)
// We're only allowed to draw the grid lines under certain circumstances.
if (_pData->IsGridLineDrawingAllowed())
{
// See GH: 803
// If we found a wide character while we looped above, it's possible we skipped over the right half
// attribute that could have contained different line information than the left half.
if (containsWideCharacter)
{
// Start from the original position in this run.
auto lineIt = currentRunItStart;
// Start from the original target in this run.
auto lineTarget = currentRunTargetStart;
// We need to go through the iterators again to ensure we get the lines associated with each
// exact column. The code above will condense two-column characters into one, but it is possible
// (like with the IME) that the line drawing characters will vary from the left to right half
// of a wider character.
// We could theoretically pre-pass for this in the loop above to be more efficient about walking
// the iterator, but I fear it would make the code even more confusing than it already is.
// Do that in the future if some WPR trace points you to this spot as super bad.
for (til::CoordType colsPainted = 0; colsPainted < cols; ++colsPainted, ++lineIt, ++lineTarget.x)
{
auto lines = lineIt->TextAttr();
_PaintBufferOutputGridLineHelper(pEngine, lines, 1, lineTarget);
}
}
else
{
// If nothing exciting is going on, draw the lines in bulk.
_PaintBufferOutputGridLineHelper(pEngine, currentRunColor, cols, screenPoint);
}
}
}
}
}
// Method Description:
// - Generates a GridLines structure from the values in the
// provided textAttribute
// Arguments:
// - textAttribute: the TextAttribute to generate GridLines from.
// Return Value:
// - a GridLineSet containing all the gridline info from the TextAttribute
GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcept
{
// Convert console grid line representations into rendering engine enum representations.
GridLineSet lines;
if (textAttribute.IsTopHorizontalDisplayed())
{
lines.set(GridLines::Top);
}
if (textAttribute.IsBottomHorizontalDisplayed())
{
lines.set(GridLines::Bottom);
}
if (textAttribute.IsLeftVerticalDisplayed())
{
lines.set(GridLines::Left);
}
if (textAttribute.IsRightVerticalDisplayed())
{
lines.set(GridLines::Right);
}
if (textAttribute.IsCrossedOut())
{
lines.set(GridLines::Strikethrough);
}
const auto underlineStyle = textAttribute.GetUnderlineStyle();
switch (underlineStyle)
{
case UnderlineStyle::NoUnderline:
break;
case UnderlineStyle::SinglyUnderlined:
lines.set(GridLines::Underline);
break;
case UnderlineStyle::DoublyUnderlined:
lines.set(GridLines::DoubleUnderline);
break;
case UnderlineStyle::CurlyUnderlined:
lines.set(GridLines::CurlyUnderline);
break;
case UnderlineStyle::DottedUnderlined:
lines.set(GridLines::DottedUnderline);
break;
case UnderlineStyle::DashedUnderlined:
lines.set(GridLines::DashedUnderline);
break;
default:
lines.set(GridLines::Underline);
break;
}
if (textAttribute.IsHyperlink())
{
lines.set(GridLines::HyperlinkUnderline);
}
return lines;
}
// Routine Description:
// - Paint helper for primary buffer output function.
// - This particular helper sets up the various box drawing lines that can be inscribed around any character in the buffer (left, right, top, underline).
// - See also: All related helpers and buffer output functions.
// Arguments:
// - textAttribute - The line/box drawing attributes to use for this particular run.
// - cchLine - The length of both pwsLine and pbKAttrsLine.
// - coordTarget - The X/Y coordinate position in the buffer which we're attempting to start rendering from.
// Return Value:
// - <none>
void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine,
const TextAttribute textAttribute,
const size_t cchLine,
const til::point coordTarget)
{
// Convert console grid line representations into rendering engine enum representations.