-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial refactor of IRenderEngine interface #10615
Initial refactor of IRenderEngine interface #10615
Conversation
@@ -462,7 +462,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |||
_lastHoveredId = newId; | |||
_lastHoveredInterval = newInterval; | |||
_renderEngine->UpdateHyperlinkHoveredId(newId); | |||
_renderer->UpdateLastHoveredInterval(newInterval); | |||
_renderEngine->UpdateLastHoveredInterval(newInterval); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fantastic example of how this PR decouples renderer.cpp
& DxEngine
.
Only DxEngine
actually needs UpdateLastHoveredInterval
. UpdateLastHoveredInterval
never should be inside renderer.cpp
.
Just by this refactoring, I'm seeing the time needed for 1 million yes dropped from 8.5s to 8.0s. Guess removal of the virtual functions does help the performance. |
@check-spelling-bot ReportUnrecognized words, please review:
Previously acknowledged words that are now absentwintelnetTo accept these unrecognized words as correct (and remove the previously acknowledged and now absent words), run the following commands... in a clone of the git@github.com:skyline75489/terminal.git repository
✏️ Contributor please read thisBy default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
If the listed items are:
See the 🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉 🗜️ If you see a bunch of garbageIf it relates to a ... well-formed patternSee if there's a pattern that would match it. If not, try writing one and adding it to a Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines. Note that patterns can't match multiline strings. binary-ish stringPlease add a file path to the File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.
|
@@ -104,15 +103,17 @@ CATCH_RETURN(); | |||
// - rectangles - One or more rectangles describing character positions on the grid | |||
// Return Value: | |||
// - S_OK | |||
[[nodiscard]] HRESULT UiaEngine::InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept | |||
[[nodiscard]] HRESULT UiaEngine::TriggerSelection(IRenderData* pData) noexcept |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very interesting change, that I'd like to ask @carlos-zamora to verify.
Ideally I want no code logic change out of this PR. This method, however, does contains logic change, but it's in a good way.
Before this PR, this is how InvalidateSelection
is called in renderer.cpp
:
LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection));
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
The reason why there's two call to InvalidateSelection
is that both GdiRendere & DxRenderer needs to first clear the previous selection before draw the current selection. Thais practically not needed inside UiaEngine!
Also, before this change _previousSelection
is stored in renderer.cpp
which UiaEngine has no access to, so it creates another _prevSelection
internally. This is no longer needed, because _previousSelection
is now moved inside RenderEngineBase
.
I see this as another example why a thinner renderer.cpp
is beneficial, because it allows more control in the engine side. And different engines do have different needs when responding to the same event.
@@ -21,39 +21,91 @@ Author(s): | |||
#pragma once | |||
namespace Microsoft::Console::Render | |||
{ | |||
struct BufferLineRenderData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is pretty badly organized and I don't have much experience in packing structs. It does not seem to cause too much performance penalty, though.
@@ -274,106 +267,6 @@ CATCH_RETURN(); | |||
return S_FALSE; | |||
} | |||
|
|||
// Routine Description: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boi do I love deleting empty implementations like these. It's satisfying for some reason. Everyone should give it a try 😄
@@ -380,7 +380,7 @@ try | |||
// statement here. | |||
|
|||
// Move the cursor to the bottom of the current viewport | |||
const short bottom = _lastViewport.BottomInclusive(); | |||
const short bottom = _viewport.BottomInclusive(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also a nice example of why we should let the actual engine have more control. The _lastViewport
was a duplicate of _viewport
in renderer.cpp
. Now it doesn't need to be duplicated anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure about that? Renderer has no interface for signaling a resize, and it provides no information in _CheckViewportAndScroll
to the render engines about what the original size was. A member that stores a "last known value" and compares it against the new one is not a duplicate. It is a design decision.
Of course, I expect that any refactoring that seeks to de-"duplicate" those takes that design need into account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is updated via engine-> UpdateViewport. All the logic are largely intact. I’ll double-check it to make sure it works as expected later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it provides no information in _CheckViewportAndScroll to the render engines about what the original size was
Before this PR, renderer.cpp
stores _viewport
using:
terminal/src/renderer/base/renderer.cpp
Line 402 in d1f152a
_viewport = Viewport::FromInclusive(srNewViewport); |
The exact same value was passed to VtEngine
using:
terminal/src/renderer/base/renderer.cpp
Line 399 in d1f152a
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport)); |
Then in VtEngine it stores the same value to _lastViewport
:
terminal/src/renderer/vt/state.cpp
Lines 218 to 220 in 79115e2
const Viewport newView = Viewport::FromInclusive(srNewViewport); | |
_lastViewport = newView; |
After this PR, I've manually checked that the same logic is now moved into RenderEngineBase::UpdateViewport
. There's only one _viewport
inside RenderEngineBase
now. The initialization and update logic remain intact.
Plus all the UT are green (!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how I feel about this. I see where you're going, but I don't like the duplication of code.
I would rather we use something like a template to generate the same thing into multiple class objects if that's what we have to do.
I'm also wondering what the additional disk cost is here to blowing up all the individual renderers with copies of things like printing the buffer lines. We might have to mitigate that by dropping out some of the renderer types from certain distributions if it's too big.
I'm wondering if maybe we'd be better served by just... drawing/writing out how we should change the thing all at once. Plan upfront and do it. I'm concerned this is part of an idea in service of a little bit extra performance.
I think we know that what we really need to do is make a big plan then move to a model that:
- Gathers all the data up and then dispatches it all at once to an engine, instead of relying on a hell of a lot of vtable calls.
- Maintain some sort of interface relationship for engines so they can be built to a uniform model as that is an advantage we have to being able to plug in multiple (and a small handful of vtable calls isn't going to harm us like the thousands we do now).
- Eliminate some unnecessary interfaces like on the IRenderData and even around the IRenderer.
- Other things I probably haven't fully articulated that I'm confident @lhecker has a better handle on than me.
@@ -21,39 +21,91 @@ Author(s): | |||
#pragma once | |||
namespace Microsoft::Console::Render | |||
{ | |||
struct BufferLineRenderData | |||
{ | |||
IRenderData* pData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know what sort of stuff is left that is needed out of IRenderData
beyond what you've already packed here? It feels like you have a lot of it already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess there isn't anything crucial left? I've managed to meet the minimum standard to not break the existing codebase., even though I don't understand some of them (for example, LienRendition
, and needLineTransformation
). I just blindly copied the old logic. With this PR, both conhost and WT run as usual. It may have glitches potentially, but at first glance this PR doesn't break the app entirely.
struct BufferLineRenderData | ||
{ | ||
IRenderData* pData; | ||
const TextBuffer& buffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh. I am seriously curious how much it will cost to just copy this and break the lock... (versus sending the ref)
@@ -71,9 +71,6 @@ namespace Microsoft::Console::Render | |||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; | |||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; | |||
|
|||
protected: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't you forget to remove some of them from this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I can't compile Bgfx & WDDM. I don't know if it can compile or not. So I just left it as it is.
RETURN_IF_FAILED(PaintBackground()); | ||
|
||
// Paint Rows of Text | ||
_LoopDirtyLines(pData, std::bind(&DxEngine::_PaintBufferLineHelper, this, std::placeholders::_1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last I checked... std::bind
is not cheap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so, too. But the trace shows that it's OK.
Thanks @miniksa . I understand most of your concerns and I wish I could minimize this PR to achieve the same thing & make everyone happy. I do believe that a thinner I'll step down from this PR now and hope this is a valuable attempt for someone in the future. |
Stale PR. Closed. |
Summary of the Pull Request
This is my proposed solution to #10610
References
PR Checklist
Detailed Description of the Pull Request / Additional comments
I've simplified the
IRenderEngine
interface with the following steps:PaintFrame(IRenderData* pData)
.renderer.cpp
intoRenderEngineBase
.S_OK
.Pros:
PaintFrame
w/o worrying about other renderers. This opens up a lot of opportunities for optimization. For example we can now remove the entire PatternId logic inVtRenderer
.renderer.cpp
, the coupling betweenrenderer.cpp
& eachIRenderEngine
is largely loosen. When adding a new method specifically for one renderer, we don't need to add empty implementation for all of them.Cons:
_PaintBufferLineHelper
) needs to be duplicated in allIRenderEngine
. This may pose challenge to future authors who want to change them.Validation Steps Performed
TODO:
TriggerSelection
does not workIME overlay in ConHost not working (but works in WT)RenderEngineBase::_titleChanged
BgfxEngine
&WDDMEngine
(I don't know how to build & run these)