Skip to content
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

Add support for double-width/double-height lines in conhost #8664

Merged
25 commits merged into from
Feb 18, 2021

Conversation

j4james
Copy link
Collaborator

@j4james j4james commented Dec 27, 2020

This PR adds support for the VT line rendition attributes, which allow
for double-width and double-height line renditions. These renditions are
enabled with the DECDWL (double-width line) and DECDHL
(double-height line) escape sequences. Both reset to the default
rendition with the DECSWL (single-width line) escape sequence. For now
this functionality is only supported by the GDI renderer in conhost.

There are a lot of changes, so this is just a general overview of the
main areas affected.

Previously it was safe to assume that the screen had a fixed width, at
least for a given point in time. But now we need to deal with the
possibility of different lines have different widths, so all the
functions that are constrained by the right border (text wrapping,
cursor movement operations, and sequences like EL and ICH) now need
to lookup the width of the active line in order to behave correctly.

Similarly it used to be safe to assume that buffer and screen
coordinates were the same thing, but that is no longer true. Lots of
places now need to translate back and forth between coordinate systems
dependent on the line rendition. This includes clipboard handling, the
conhost color selection and search, accessibility location tracking and
screen reading, IME editor positioning, "snapping" the viewport, and of
course all the rendering calculations.

For the rendering itself, I've had to introduce a new
PrepareLineTransform method that the render engines can use to setup
the necessary transform matrix for a given line rendition. This is also
now used to handle the horizontal viewport offset, since that could no
longer be achieved just by changing the target coordinates (on a double
width line, the viewport offset may be halfway through a character).

I've also had to change the renderer's existing InvalidateCursor
method to take a SMALL_RECT rather than a COORD, to allow for the
cursor being a variable width. Technically this was already a problem,
because the cursor could occupy two screen cells when over a
double-width character, but now it can be anything between one and four
screen cells (e.g. a double-width character on the double-width line).

In terms of architectural changes, there is now a new lineRendition
field in the ROW class that keeps track of the line rendition for each
row, and several new methods in the ROW and TextBuffer classes for
manipulating that state. This includes a few helper methods for handling
the various issues discussed above, e.g. position clamping and
translating between coordinate systems.

Validation Steps Performed

I've manually confirmed all the double-width and double-height tests in
Vttest are now working as expected, and the VT100 Torture Test now
renders correctly (at least the line rendition aspects). I've also got
my own test scripts that check many of the line rendition boundary cases
and have confirmed that those are now passing.

I've manually tested as many areas of the conhost UI that I could think
of, that might be affected by line rendition, including things like
searching, selection, copying, and color highlighting. For
accessibility, I've confirmed that the Magnifier and Narrator
correctly handle double-width lines. And I've also tested the Japanese
IME, which while not perfect, is at least useable.

Closes #7865

@ghost ghost added Area-VT Virtual Terminal sequence support Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Conhost For issues in the Console codebase labels Dec 27, 2020
@j4james
Copy link
Collaborator Author

j4james commented Dec 27, 2020

I've tried to keep this as simple and efficient as I could, but it touches on almost every part of the system, so it seems inevitable that it's going to have some impact on performance. And given that there isn't much demand for this functionality, I'd completely understand if you decide not to merge this.

That said, here are a couple of screenshots so you can see how well it handles VT Test and the VT100 Torture Test.

image

image

@j4james j4james marked this pull request as ready for review December 27, 2020 23:14
@DHowett
Copy link
Member

DHowett commented Dec 29, 2020

wow

@zadjii-msft zadjii-msft self-assigned this Jan 4, 2021
@miniksa miniksa self-assigned this Jan 4, 2021
@miniksa
Copy link
Member

miniksa commented Jan 4, 2021

I've tried to keep this as simple and efficient as I could, but it touches on almost every part of the system, so it seems inevitable that it's going to have some impact on performance. And given that there isn't much demand for this functionality, I'd completely understand if you decide not to merge this.

James... with high quality contributions like this, it would be a crime for us to not merge it at some point. I'll even catch your back with the performance implications and writing out the DX version of the rendering if I must. It just might take us a little bit of time to digest the whole thing before we can take it.

Once again, thank you so much. I hope to read through it soon.

Copy link
Member

@miniksa miniksa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good. Just a couple questions and such.

I'll pull a perf trace on this branch and master with GDI to see if there's any noticeable detriment to this in the common case where it's unused.

src/buffer/out/LineRendition.hpp Show resolved Hide resolved
src/buffer/out/Row.hpp Show resolved Hide resolved
src/buffer/out/textBuffer.hpp Outdated Show resolved Hide resolved
src/buffer/out/textBuffer.cpp Show resolved Hide resolved
src/buffer/out/textBuffer.cpp Show resolved Hide resolved
src/renderer/base/renderer.cpp Show resolved Hide resolved
src/renderer/gdi/paint.cpp Show resolved Hide resolved
src/renderer/uia/UiaRenderer.hpp Show resolved Hide resolved
src/renderer/wddmcon/WddmConRenderer.cpp Show resolved Hide resolved
src/terminal/adapter/adaptDispatch.cpp Show resolved Hide resolved
@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 15, 2021
@miniksa
Copy link
Member

miniksa commented Jan 15, 2021

This is the only thing I noticed in writing big.txt between main and this branch. Note that big.txt doesn't output anything but standard text in standard lines.

image

It looks like we're calling Set/ModifyWorldTransform to GDI even when there isn't really anything to talk about. Unfortunately when I expand it, it looks like this goes all the way into kernel to resolve that there's nothing to change. So if we could not call it when there's nothing to be done on our side, it would probably save us something.

image

It does look like its a very small amount in CPU time, but there's probably some amount of thunk/kernel switching that isn't being accounted for here too that we could avoid.

@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 16, 2021
@j4james
Copy link
Collaborator Author

j4james commented Jan 16, 2021

It looks like we're calling Set/ModifyWorldTransform to GDI even when there isn't really anything to talk about.

I'm really glad you spotted that. I actually had checks in place to avoid setting and resetting the transform unnecessarily in the PrepareLineTransform and ResetLineTransform methods, but I got caught out by the fact that -0.0 and 0.0 aren't equal.

And I just realised there's also the special case handling for the inverted cursor, which has to "uninvert" itself when scrolling, and that requires setting a transform first. So that's also going to need a check to avoid the transform when it's just the identity matrix.

@j4james
Copy link
Collaborator Author

j4james commented Jan 18, 2021

OK. I've added the requested comments, made the GetTextRects parameters explicit, and improved the checks to avoid unnecessary transform changes. You shouldn't be seeing any Set/ModifyWorldTransform calls now if you're just rendering regular single-width text, with no horizontal viewport offset.

Copy link
Member

@miniksa miniksa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. Thanks, @j4james.

Copy link

@hixio-mh hixio-mh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge pull requested #8864 and close #8867

Copy link

@hixio-mh hixio-mh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close

@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 25, 2021
@zadjii-msft
Copy link
Member

@hafixo Please stop commenting on unrelated PRs without actionable feedback.

@ghost ghost added the No-Recent-Activity This issue/PR is going stale and may be auto-closed without further activity. label Feb 1, 2021
@ghost
Copy link

ghost commented Feb 1, 2021

This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment.

@DHowett
Copy link
Member

DHowett commented Feb 1, 2021

Sorry, @j4james, this is all on me. I've been travelling and not had a chance to look over this in-depth. I'll clear the stale state and get it in this week. 😄

Thanks again for excellent work.

Comment on lines +1962 to +1963
// Reset the line rendition for all of these rows.
success = success && _pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, csbiex.srWindow.Bottom);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, we fill after we reset the rendition? I don't totally understand why, but I trust your homework here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

erm we fill before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it matters. The fill operation doesn't care about line renditions, so I suspect it would work in either order. I do know that this sequence works correctly as is - it's one of my manual test cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just double checked this and the order doesn't make any difference. In both case the lines are reset to single width and the screen is completely filled with Es.

@j4james
Copy link
Collaborator Author

j4james commented Feb 18, 2021

FYI, I'm going to bed now, so if there is many more merging or changes required it'll have to wait until tomorrow.

@DHowett DHowett added the AutoMerge Marked for automatic merge by the bot when requirements are met label Feb 18, 2021
@ghost
Copy link

ghost commented Feb 18, 2021

Hello @DHowett!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 4c53c59 into microsoft:main Feb 18, 2021
@DHowett
Copy link
Member

DHowett commented Feb 18, 2021

Beautiful work with great attention to detail, as always. Thanks again @j4james!

@christianparpart
Copy link

What the... I am very glad to see you having implemented this. Good work!

@DHowett
Copy link
Member

DHowett commented Mar 17, 2021

This is available in conhost as of Windows Insider Preview build 21337. Congrats! 😄

@j4james j4james deleted the feature-line-attributes branch April 3, 2021 21:08
Copy link

@hixio-mh hixio-mh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#8664
Reviews and change

ghost pushed a commit that referenced this pull request May 18, 2022
This PR adds support for the VT line rendition attributes in the DirectX
renderer, which allows for double-width and double-height line
renditions.

Line renditions were first implemented in conhost (with the GDI
renderer) in PR #8664.  Supporting them in the DX renderer now is a
small step towards #11595.

The DX implementation is very similar to the GDI one. When a particular
line rendition is requested, we create a transform that is applied to
the render target. And in the case of double-height renditions, we also
initialize some clipping offsets to allow for the fact that we only
render half of a line at a time.

One additional complication exists when drawing the cursor, which
requires a two part process where it first renders to a command list,
and then draw the command list in a second step. We need to temporarily
reset the transform in that first stage otherwise it ends up being
applied twice.

I've manually tested the renderer in conhost by setting the `UseDx`
registry entry and confirmed that it passes the _Vttest_ double-size
tests as well as several of my own tests. I've also checked that the
renderer can now handle horizontal scrolling, which is a feature we get
for free with the transforms.
ghost pushed a commit that referenced this pull request Sep 9, 2022
This PR introduces a mechanism for passing through line rendition
attributes to the conpty client, so we can support double-width and
double-height text in Windows Terminal.

Line renditions were first implemented in conhost (with the GDI
renderer) in PR #8664, and were implemented in the DX renderer in PR
#13102.

By default this won't add any additional overhead to the conpty output,
but as soon as there is any usage of double-size text, we switch to a
mode in which every line output will be prefixed with a line rendition
sequence. This is to ensure that the line attributes in the client
terminal are always in sync with the host.

Since this does add some overhead to the conpty output, we'd prefer not
to remain in this mode longer than necessary. So whenever there is a
full repaint of the entire viewport, we check to see if all of the lines
are single-width. If that is the case, we can then safely skip the line
rendition sequences in future updates.

One other small optimization is when the conpty update is only writing
out a single character (this is something we already check for). When
that is the case, we can safely skip the line rendition prefix, because
a single character update should never include a change of the line
rendition.

## Validation Steps Performed

I've manually tested that Windows Terminal now passes the double-size
tests in _Vttest_, and also confirmed various edge cases are working
correctly in my own double-size tests.

Closes #11595
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-VT Virtual Terminal sequence support AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Conhost For issues in the Console codebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for double width/height lines
6 participants