-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Can we get rid of the IRenderTarget interface? #12551
Comments
/cc @lhecker in case this messes with your plans for the renderer. |
I'm planning for the renderer to decompress the viewport into a buffer, so that each rendering engine can directly access the string of text on each row and don't need to use complex iterators in order to parse run-length encoded attributes. So instead of calling each rendering engine with I believe this won't interfere with your plans. 🙂 Apart from that I think it's a bit weird that the
Weirdly enough your other interface-related changes in the past also didn't show a noticeable performance improvement for me. But it seems they have quite an effect when we build our official PGO'd releases of Windows Terminal, because 1.13 has a ~16% faster VT throughput than 1.12. |
In some cases it may be appropriate for the caller to trigger the redraw directly on the renderer, assuming it's a "global" update. But when the update is the result of a buffer modification, they're going to need to check whether they're working with the active buffer, otherwise the redraw shouldn't be happening. So rather than having all of those callers doing something like
I figured it would cleaner to let the buffer handle the check, and then the caller just uses:
That way all of those active checks are happening in once place, and there should be less code bloat.
Wow! That's good to know. I was rather disappointed with the results I was seeing for those interface changes, because I was convinced we should be getting at least some boost from removing the virtuals. So maybe there's a chance this refactor could still have some performance benefit too. |
IMHO the |
There's not just two instances of
But that's already the case now - the
Maybe I'm not understanding what you mean, because I can't see how that would possibly work.
That is my ultimate goal. So at least we agree on something. 😉 |
Well now I agree on everything you say. 😄
I'm not familiar with our VT implementation yet. But personally I was hoping to have a function akin to textBuffer.Write(VtVersion::Xterm256, L"\x1b[123;456X"); ...and implement all of the Console API on top of that function. Any VT state would be part of the |
Your idea of a Then in the I fully appreciate the desire to simplify and cut down on the number of classes, and I'm doing my best to achieve that, but it's not going to happen overnight (at least not at the rate I'm going 😉). I think we just need to take one step at a time, and try to make sure that each step is moving us in the right direction. |
## Summary of the Pull Request The purpose of the `IRenderTarget` interface was to support the concept of multiple buffers in conhost. When a text buffer needed to trigger a redraw, the render target implementation would be responsible for deciding whether to forward that redraw to the renderer, depending on whether the buffer was active or not. This PR instead introduces a flag in the `TextBuffer` class to track whether it is active or not, and it can then simply check the flag to decide whether it needs to trigger a redraw or not. That way it can work with the `Renderer` directly, and we can avoid a bunch of virtual calls for the various redraw methods. ## PR Checklist * [x] Closes #12551 * [x] CLA signed. * [ ] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [x] I've discussed this with core contributors already. Issue number where discussion took place: #12551 ## Detailed Description of the Pull Request / Additional comments Anywhere that had previously been getting an `IRenderTarget` from the `TextBuffer` class in order to trigger a redraw, will now just call the `TriggerRedraw` method on the `TextBuffer` class itself, since that will handle the active check which used to be the responsibility of the render target. All the redraw methods that were in `IRenderTarget` are now exposed in `TextBuffer` instead. For this to work, though, the `Renderer` needed to be available before the `TextBuffer` could be constructed, which required a change in the conhost initialization order. In the `ConsoleInitializeConnectInfo` function, I had to move the `Renderer` initialization up so it could be constructed before the call to `SetUpConsole` (which initializes the buffer). Once both are ready, the `Renderer::EnablePainting` method is called to start the render thread. The other catch is that the renderer used to setup its initial viewport in the constructor, but with the new initialization order, the viewport size would not be known at that point. I've addressed that problem by moving the viewport initialization into the `EnablePainting` method, since that will only be called after the buffer is setup. ## Validation Steps Performed The changes in architecture required a number of tweaks to the unit tests. Some were using a dummy `IRenderTarget` implementation which now needed to be replaced with a full `Renderer` (albeit with mostly null parameters). In the case of the scroll test, a mock `IRenderTarget` was used to track the scroll delta, which now had to be replaced with a mock `RenderEngine` instead. Some tests previously relied on having just a `TextBuffer` but without a `Renderer`, and now they require both. And tests that were constructing the `TextBuffer` and `Renderer` themselves had to be updated to use the new initialization order, i.e. with the renderer constructed first. Semantically, though, the tests still essentially work the same way as they did before, and they all still pass.
🎉This issue was addressed in #12568, which has now been successfully released as Handy links: |
Description of the new feature/enhancement
As I understand it, the
IRenderTarget
interface is primarily used by theTextBuffer
class to support the concept of multiple buffers in conhost. So when a text buffer triggers a redraw, that goes through theScreenBufferRenderTarget
implementation, and it'll only forward the call to the renderer if we're in the active buffer.But what if we added a flag in the
TextBuffer
class to track if it is the active buffer or not? That's easy to update whenever the active buffer changes, and then we can deal with the renderer directly. When the buffer needs to trigger a redraw, it simply checks the flag to see if it's active before forwarding the request to the renderer.I've been working on an PR for this, and it looks to me like it's doable. It doesn't appear to make a great difference to the performance one way or the other, but it does shrink the binary, and I think it makes the code a little simpler. My main reason for wanting this, though, is because I'm hoping it will help with the
AdaptDispatch
refactoring I'm working on for #3849.Proposed technical implementation details (optional)
TextBuffer
gets a new field which tracks whether it's active or not, and has a reference to the actualRenderer
class rather thanIRenderTarget
. When it needs to trigger a redraw (or any of the otherIRenderTarget
methods), it checks whether it's active or not before forwarding the request to the renderer.Anywhere that is currently getting a
IRenderTarget
from theTextBuffer
in order to trigger a redraw, would now just call theTriggerRedraw
method on theTextBuffer
class itself, since that will handle the active check which would previously have been the responsibility of the render target. All the trigger methods that are currently inIRenderTarget
would now be exposed inTextBuffer
.One catch with this approach is that we'd need to rethink the initialization order in conhost. In the
ConsoleInitializeConnectInfo
function, we'd need to move theRenderer
initialization up so it's constructed first, and only after that call theSetUpConsole
function to initialize the buffer. Once they're both initialized, we can then call theRenderer::EnablePainting
method to allow the render thread to start.The other catch is that the renderer tries to setup its initial viewport in the constructor, but at that point in time the viewport size would be unknown. However, that can easily be addressed by moving the viewport initialization into the
EnablePainting
method, since that would still be called after the buffer is setup (as mentioned above).Note that this order of initialization more closely matches the Windows Terminal implementation, which is one of the reasons why I think it'll be beneficial for #3849.
On the whole, the changes needed to make this work look fairly straightforward. The biggest pain is getting the unit tests to work again, because some of them rely on the
IRenderTarget
interface for mocking the renderer, and others depend on the order of initialization. They're all fixable, but it might be a bit messy.The text was updated successfully, but these errors were encountered: