Bug 1413178 - Update webrender to commit fae962bfd6e1997f4b921ee93c3c1cc5abca3256. r=lsalzman

MozReview-Commit-ID: LStxYqdw50U

--HG--
extra : rebase_source : a732f4a3e6ed28902d61de03dfea69626733711d
This commit is contained in:
Kartikaya Gupta 2017-11-03 10:28:15 -04:00
Родитель 65a3470bf9
Коммит 5a6135f9dd
23 изменённых файлов: 1047 добавлений и 269 удалений

Просмотреть файл

@ -175,4 +175,4 @@ Troubleshooting tips:
-------------------------------------------------------------------------------
The version of WebRender currently in the tree is:
c0194de78ce26106a8497484dc8d159069e3a482
fae962bfd6e1997f4b921ee93c3c1cc5abca3256

Просмотреть файл

@ -0,0 +1,684 @@
# Text Rendering
This document describes the details of how WebRender renders text, particularly the blending stage of text rendering.
We will go into grayscale text blending, subpixel text blending, and "subpixel text with background color" blending.
### Prerequisites
The description below assumes you're familiar with regular rgba compositing, operator over,
and the concept of premultiplied alpha.
### Not covered in this document
We are going to treat the origin of the text mask as a black box.
We're also going to assume we can blend text in the device color space and will not go into the gamma correction and linear pre-blending that happens in some of the backends that produce the text masks.
## Grayscale Text Blending
Grayscale text blending is the simplest form of text blending. Our blending function has three inputs:
- The text color, as a premultiplied rgba color.
- The text mask, as a single-channel alpha texture.
- The existing contents of the framebuffer that we're rendering to, the "destination". This is also a premultiplied rgba buffer.
Note: The word "grayscale" here does *not* mean that we can only draw gray text.
It means that the mask only has a single alpha value per pixel, so we can visualize
the mask in our minds as a grayscale image.
### Deriving the math
We want to mask our text color using the single-channel mask, and composite that to the destination.
This compositing step uses operator "over", just like regular compositing of rgba images.
I'll be using GLSL syntax to describe the blend equations, but please consider most of the code below pseudocode.
We can express the blending described above as the following blend equation:
```glsl
vec4 textblend(vec4 text_color, vec4 mask, vec4 dest) {
return over(in(text_color, mask), dest);
}
```
with `over` being the blend function for (premultiplied) operator "over":
```glsl
vec4 over(vec4 src, vec4 dest) {
return src + (1.0 - src.a) * dest;
}
```
and `in` being the blend function for (premultiplied) operator "in", i.e. the masking operator:
```glsl
vec4 in(vec4 src, vec4 mask) {
return src * mask.a;
}
```
So the complete blending function is:
```glsl
result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r;
result.g = text_color.g * mask.a + (1.0 - text_color.a * mask.a) * dest.g;
result.b = text_color.b * mask.a + (1.0 - text_color.a * mask.a) * dest.b;
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
```
### Rendering this with OpenGL
In general, a fragment shader does not have access to the destination.
So the full blend equation needs to be expressed in a way that the shader only computes values that are independent of the destination,
and the parts of the equation that use the destination values need to be applied by the OpenGL blend pipeline itself.
The OpenGL blend pipeline can be tweaked using the functions `glBlendEquation` and `glBlendFunc`.
In our example, the fragment shader can output just `text_color * mask.a`:
```glsl
oFragColor = text_color * mask.a;
```
and the OpenGL blend pipeline can be configured like so:
```rust
pub fn set_blend_mode_premultiplied_alpha(&self) {
self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
self.gl.blend_equation(gl::FUNC_ADD);
}
```
This results in an overall blend equation of
```
result.r = 1 * oFragColor.r + (1 - oFragColor.a) * dest.r;
^ ^ ^^^^^^^^^^^^^^^^^
| | |
+--gl::ONE | +-- gl::ONE_MINUS_SRC_ALPHA
|
+-- gl::FUNC_ADD
= 1 * (text_color.r * mask.a) + (1 - (text_color.a * mask.a)) * dest.r
= text_color.r * mask.a + (1 - text_color.a * mask.a) * dest.r
```
which is exactly what we wanted.
### Differences to the actual WebRender code
There are two minor differences between the shader code above and the actual code in the text run shader in WebRender:
```glsl
oFragColor = text_color * mask.a; // (shown above)
// vs.
oFragColor = vColor * mask * alpha; // (actual webrender code)
```
`vColor` is set to the text color. The differences are:
- WebRender multiplies with all components of `mask` instead of just with `mask.a`.
However, our font rasterization code fills the rgb values of `mask` with the value of `mask.a`,
so this is completely equivalent.
- WebRender applies another alpha to the text. This is coming from the clip.
You can think of this alpha to be a pre-adjustment of the text color for that pixel, or as an
additional mask that gets applied to the mask.
## Subpixel Text Blending
Now that we have the blend equation for single-channel text blending, we can look at subpixel text blending.
The main difference between subpixel text blending and grayscale text blending is the fact that,
for subpixel text, the text mask contains a separate alpha value for each color component.
### Component alpha
Regular painting uses four values per pixel: three color values, and one alpha value. The alpha value applies to all components of the pixel equally.
Imagine for a second a world in which you have *three alpha values per pixel*, one for each color component.
- Old world: Each pixel has four values: `color.r`, `color.g`, `color.b`, and `color.a`.
- New world: Each pixel has *six* values: `color.r`, `color.a_r`, `color.g`, `color.a_g`, `color.b`, and `color.a_b`.
In such a world we can define a component-alpha-aware opererator "over":
```glsl
vec6 over_comp(vec6 src, vec6 dest) {
vec6 result;
result.r = src.r + (1.0 - src.a_r) * dest.r;
result.g = src.g + (1.0 - src.a_g) * dest.g;
result.b = src.b + (1.0 - src.a_b) * dest.b;
result.a_r = src.a_r + (1.0 - src.a_r) * dest.a_r;
result.a_g = src.a_g + (1.0 - src.a_g) * dest.a_g;
result.a_b = src.a_b + (1.0 - src.a_b) * dest.a_b;
return result;
}
```
and a component-alpha-aware operator "in":
```glsl
vec6 in_comp(vec6 src, vec6 mask) {
vec6 result;
result.r = src.r * mask.a_r;
result.g = src.g * mask.a_g;
result.b = src.b * mask.a_b;
result.a_r = src.a_r * mask.a_r;
result.a_g = src.a_g * mask.a_g;
result.a_b = src.a_b * mask.a_b;
return result;
}
```
and even a component-alpha-aware version of `textblend`:
```glsl
vec6 textblend_comp(vec6 text_color, vec6 mask, vec6 dest) {
return over_comp(in_comp(text_color, mask), dest);
}
```
This results in the following set of equations:
```glsl
result.r = text_color.r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.r;
result.g = text_color.g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.g;
result.b = text_color.b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.b;
result.a_r = text_color.a_r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.a_r;
result.a_g = text_color.a_g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.a_g;
result.a_b = text_color.a_b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.a_b;
```
### Back to the real world
If we want to transfer the component alpha blend equation into the real world, we need to make a few small changes:
- Our text color only needs one alpha value.
So we'll replace all instances of `text_color.a_r/g/b` with `text_color.a`.
- We're currently not making use of the mask's `r`, `g` and `b` values, only of the `a_r`, `a_g` and `a_b` values.
So in the real world, we can use the rgb channels of `mask` to store those component alphas and
replace `mask.a_r/g/b` with `mask.r/g/b`.
These two changes give us:
```glsl
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
result.a_r = text_color.a * mask.r + (1.0 - text_color.a * mask.r) * dest.a_r;
result.a_g = text_color.a * mask.g + (1.0 - text_color.a * mask.g) * dest.a_g;
result.a_b = text_color.a * mask.b + (1.0 - text_color.a * mask.b) * dest.a_b;
```
There's a third change we need to make:
- We're rendering to a destination surface that only has one alpha channel instead of three.
So `dest.a_r/g/b` and `result.a_r/g/b` will need to become `dest.a` and `result.a`.
This creates a problem: We're currently assigning different values to `result.a_r`, `result.a_g` and `result.a_b`.
Which of them should we use to compute `result.a`?
This question does not have an answer. One alpha value per pixel is simply not sufficient
to express the same information as three alpha values.
However, see what happens if the destination is already opaque:
We have `dest.a_r == 1`, `dest.a_g == 1`, and `dest.a_b == 1`.
```
result.a_r = text_color.a * mask.r + (1 - text_color.a * mask.r) * dest.a_r
= text_color.a * mask.r + (1 - text_color.a * mask.r) * 1
= text_color.a * mask.r + 1 - text_color.a * mask.r
= 1
same for result.a_g and result.a_b
```
In other words, for opaque destinations, it doesn't matter what which channel of the mask we use when computing `result.a`, the result will always be completely opaque anyways. In WebRender we just pick `mask.g` (or rather,
have font rasterization set `mask.a` to the value of `mask.g`) because it's as good as any.
The takeaway here is: **Subpixel text blending is only supported for opaque destinations.** Attempting to render subpixel
text into partially transparent destinations will result in bad alpha values. Or rather, it will result in alpha values which
are not anticipated by the r, g, and b values in the same pixel, so that subsequent blend operations, which will mix r and a values
from the same pixel, will produce incorrect colors.
Here's the final subpixel blend function:
```glsl
vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
vec4 result;
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
return result;
}
```
or for short:
```glsl
vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
return text_color * mask + (1.0 - text_color.a * mask) * dest;
}
```
To recap, here's what we gained and lost by making the transition from the full-component-alpha world to the
regular rgba world: All colors and textures now only need four values to be represented, we still use a
component alpha mask, and the results are equivalent to the full-component-alpha result assuming that the
destination is opaque. We lost the ability to draw to partially transparent destinations.
### Making this work in OpenGL
We have the complete subpixel blend function.
Now we need to cut it into pieces and mix it with the OpenGL blend pipeline in such a way that
the fragment shader does not need to know about the destination.
Compare the equation for the red channel and the alpha channel between the two ways of text blending:
```
single-channel alpha:
result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
component alpha:
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
```
Notably, in the single-channel alpha case, all three destination color channels are multiplied with the same thing:
`(1.0 - text_color.a * mask.a)`. This factor also happens to be "one minus `oFragColor.a`".
So we were able to take advantage of OpenGL's `ONE_MINUS_SRC_ALPHA` blend func.
In the component alpha case, we're not so lucky: Each destination color channel
is multiplied with a different factor. We can use `ONE_MINUS_SRC_COLOR` instead,
and output `text_color.a * mask` from our fragment shader.
But then there's still the problem that the first summand of the computation for `result.r` uses
`text_color.r * mask.r` and the second summand uses `text_color.a * mask.r`.
There's no way around it, we have to use two passes.
(Actually, there is a way around it, but it requires the use of `glBlendColor`, which we want to avoid because
we'd have to use different draw calls for different text colors, or it requires "dual source blending" which is
not supported everywhere.)
Here's how we can express the subpixel text blend function with two passes:
- The first pass outputs `text_color.a * mask` from the fragment shader and
uses `gl::ZERO, gl::ONE_MINUS_SRC_COLOR` as the glBlendFuncs. This achieves:
```
oFragColor = text_color.a * mask;
result_after_pass0.r = 0 * oFragColor.r + (1 - oFragColor.r) * dest.r
= (1 - text_color.a * mask.r) * dest.r
result_after_pass0.g = 0 * oFragColor.g + (1 - oFragColor.g) * dest.r
= (1 - text_color.a * mask.r) * dest.r
...
```
- The second pass outputs `text_color * mask` from the fragment shader and uses
`gl::ONE, gl::ONE` as the glBlendFuncs. This gets us:
```
oFragColor = text_color * mask;
result_after_pass1.r
= 1 * oFragColor.r + 1 * result_after_pass0.r
= text_color.r * mask.r + result_after_pass0.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
```
And analogous results for the other channels.
This achieves what we set out to do, so we're done here.
## Subpixel Text Rendering to Transparent Destinations with a Background Color Hint
### Motivation
As we've seen in the previous section, subpixel text drawing has the limitation that it only works on opaque destinations.
In other words, if you use the `subpixeltextblend` function to draw something to a transparent surface,
and then composite that surface onto on opaque background,
the result will generally be different from drawing the text directly onto the opaque background.
Let's express that inequality in code.
```
- vec4 text_color
- vec4 mask
- vec4 transparency = vec4(0.0, 0.0, 0.0, 0.0)
- vec4 background with background.a == 1.0
over(subpixeltextblend(text_color, mask, transparency), background).rgb
is, in general, not equal to
subpixeltextblend(text_color, mask, background).rgb
```
However, one interesting observation is that if the background is black, the two *are* equal:
```
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
over(subpixeltextblend(text_color, mask, transparency), black).r
= subpixeltextblend(text_color, mask, transparency).r +
(1 - subpixeltextblend(text_color, mask, transparency).a) * black.r
= subpixeltextblend(text_color, mask, transparency).r +
(1 - subpixeltextblend(text_color, mask, transparency).a) * 0
= subpixeltextblend(text_color, mask, transparency).r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * transparency.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * 0
= text_color.r * mask.r + (1 - text_color.a * mask.r) * black.r
= subpixeltextblend(text_color, mask, black).r
```
So it works out for black backgrounds. The further your *actual* background color gets away from black,
the more incorrect your result will be.
If it works for black, is there a way to make it work for other colors?
This is the motivating question for this third way of text blending:
We want to be able to specify an *estimated background color*, and have a blending function
`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`,
in such a way that the error we get by using an intermediate surface is somehow in relation
to the error we made when estimating the background color. In particular, if we estimated
the background color perfectly, we want the intermediate surface to go unnoticed.
Expressed as code:
```
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, bg_color)
```
This is one of three constraints we'd like `subpixeltextblend_withbgcolor` to satisfy.
The next constraint is the following: If `dest` is already opaque, `subpixeltextblend_withbgcolor`
should have the same results as `subpixeltextblend`, and the background color hint should be ignored.
```
If dest.a == 1.0,
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
should always be equal to
subpixeltextblend(text_color, mask, dest)
```
And there's a third condition we'd like it to fulfill:
In places where the mask is zero, the destination should be unaffected.
```
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
should always be equal to
dest
```
### Use cases
The primary use case for such a blend method is text on top of vibrant areas of a window on macOS.
Vibrant backgrounds with behind-window blending are computed by the window server, and they are tinted
in a color that's based on the chosen vibrancy type.
The window's rgba buffer is transparent in the vibrant areas. Window contents, even text, are drawn onto
that transparent rgba buffer. Then the window server composites the window onto an opaque backdrop.
So the results on the screen are computed as follows:
```glsl
window_buffer_pixel = subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency);
screen_pixel = over(window_buffer_pixel, window_backdrop);
```
### Prior art
Apple has implemented such a method of text blending in CoreGraphics, specifically for rendering text onto vibrant backgrounds.
It's hidden behind the private API `CGContextSetFontSmoothingBackgroundColor` and is called by AppKit internally before
calling the `-[NSView drawRect:]` method of your `NSVisualEffectView`, with the appropriate font smoothing background color
for the vibrancy type of that view.
I'm not aware of any public documentation of this way of text blending.
It seems to be considered an implementation detail by Apple, and is probably hidden by default because it can be a footgun:
If the font smoothing background color you specify is very different from the actual background that our surface is placed
on top of, the text will look glitchy.
### Deriving the blending function from first principles
Before we dive into the math, let's repeat our goal once more.
We want to create a blending function of the form
`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`
(with `bg_color` being an opaque color)
which satisfies the following three constraints:
```
Constraint I:
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, bg_color)
Constraint II:
If dest.a == 1.0,
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
should always be equal to
subpixeltextblend(text_color, mask, dest)
Constraint II:
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
should always be equal to
dest
```
Constraint I and constraint II are about what happens depending on the destination's alpha.
In particular: If the destination is completely transparent, we should blend into the
estimated background color, and if it's completely opaque, we should blend into the destination color.
In fact, we really want to blend into `over(dest, bg_color)`: we want `bg_color` to be used
as a backdrop *behind* the current destination. So let's combine constraints I and II into a new
constraint IV:
```
Constraint IV:
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, over(dest, bg_color))
```
Let's look at just the left side of that equation and rejiggle it a bit:
```
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r
= subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r +
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
<=>
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
```
Now insert the right side of constraint IV:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
= over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
```
Our blend function is almost finished. We just need select an alpha for our result.
Constraints I, II and IV don't really care about the alpha value. But constraint III requires that:
```
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest).a
should always be equal to
dest.a
```
so the computation of the alpha value somehow needs to take into account the mask.
Let's say we have an unknown function `make_alpha(text_color.a, mask)` which returns
a number between 0 and 1 and which is 0 if the mask is entirely zero, and let's defer
the actual implementation of that function until later.
Now we can define the alpha of our overall function using the `over` function:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a
:= make_alpha(text_color.a, mask) + (1 - make_alpha(text_color.a, mask)) * dest.a
```
We can plug this in to our previous result:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r
- (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r
- (1 - (make_alpha(text_color.a, mask) +
(1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * over(dest, bg_color).r
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (dest.r + (1 - dest.a) * bg_color.r)
- (text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - make_alpha(text_color.a, mask)
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- ((1 - make_alpha(text_color.a, mask)) * 1
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- ((1 - make_alpha(text_color.a, mask)) * (1 - dest.a)) * bg_color.r
= text_color.r * mask.r
+ dest.r - text_color.a * mask.r * dest.r
+ (1 - dest.a) * bg_color.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - dest.a) * bg_color.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ ((1 - text_color.a * mask.r)
- (1 - make_alpha(text_color.a, mask))) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - text_color.a * mask.r
- 1 + make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (make_alpha(text_color.a, mask) - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
```
We now have a term of the form `A + B + C`, with `A` and `B` being guaranteed to
be between zero and one.
We also want `C` to be between zero and one.
We can use this restriction to help us decide on an implementation of `make_alpha`.
If we define `make_alpha` as
```glsl
float make_alpha(text_color_a, mask) {
float max_rgb = max(max(mask.r, mask.g), mask.b);
return text_color_a * max_rgb;
}
```
, then `(make_alpha(text_color.a, mask) - text_color.a * mask.r)` becomes
`(text_color.a * max(max(mask.r, mask.g), mask.b) - text_color.a * mask.r)`, which is
`text_color.a * (max(max(mask.r, mask.g), mask.b) - mask.r)`, and the subtraction will
always yield something that's greater or equal to zero for r, g, and b,
because we will subtract each channel from the maximum of the channels.
Putting this all together, we have:
```glsl
vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest) {
float max_rgb = max(max(mask.r, mask.g), mask.b);
vec4 result;
result.r = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r +
text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a);
result.g = text_color.g * mask.g + (1 - text_color.a * mask.g) * dest.g +
text_color.a * bg_color.g * (max_rgb - mask.g) * (1 - dest.a);
result.b = text_color.b * mask.b + (1 - text_color.a * mask.b) * dest.b +
text_color.a * bg_color.b * (max_rgb - mask.b) * (1 - dest.a);
result.a = text_color.a * max_rgb + (1 - text_color.a * max_rgb) * dest.a;
return result;
}
```
This is the final form of this blend function. It satisfies all of the four constraints.
### Implementing it with OpenGL
Our color channel equations consist of three pieces:
- `text_color.r * mask.r`, which simply gets added to the rest.
- `(1 - text_color.a * mask.r) * dest.r`, a factor which gets multiplied with the destination color.
- `text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a)`, a factor which gets multiplied
with "one minus destination alpha".
We will need three passes. Each pass modifies the color channels in the destination.
This means that the part that uses `dest.r` needs to be applied first.
Then we can apply the part that uses `1 - dest.a`.
(This means that the first pass needs to leave `dest.a` untouched.)
And the final pass can apply the `result.a` equation and modify `dest.a`.
```
pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
}
Pass0:
oFragColor = vec4(text.color.a) * mask;
Pass1:
oFragColor = vec4(text.color.a) * text.bg_color * (vec4(mask.a) - mask);
Pass2:
oFragColor = text.color * mask;
result_after_pass0.r = 0 * (text_color.a * mask.r) + (1 - text_color.a * mask.r) * dest.r
result_after_pass0.a = 0 * (text_color.a * mask.a) + 1 * dest.a
result_after_pass1.r = (1 - result_after_pass0.a) * (text_color.a * (mask.max_rgb - mask.r) * bg_color.r) + 1 * result_after_pass0.r
result_after_pass1.a = 0 * (text_color.a * (mask.max_rgb - mask.a) * bg_color.a) + 1 * result_after_pass0.a
result_after_pass2.r = 1 * (text_color.r * mask.r) + 1 * result_after_pass1.r
result_after_pass2.a = 1 * (text_color.a * mask.max_rgb) + (1 - text_color.a * mask.max_rgb) * result_after_pass1.a
```
Instead of computing `max_rgb` in the shader, we can just require the font rasterization code to fill
`mask.a` with the `max_rgb` value.

Просмотреть файл

@ -74,7 +74,7 @@ vec4[2] fetch_from_resource_cache_2(int address) {
#define VECS_PER_LAYER 9
#define VECS_PER_RENDER_TASK 3
#define VECS_PER_PRIM_HEADER 2
#define VECS_PER_TEXT_RUN 2
#define VECS_PER_TEXT_RUN 3
#define VECS_PER_GRADIENT 3
#define VECS_PER_GRADIENT_STOP 2
@ -717,13 +717,14 @@ Rectangle fetch_rectangle(int address) {
struct TextRun {
vec4 color;
vec4 bg_color;
vec2 offset;
int subpx_dir;
};
TextRun fetch_text_run(int address) {
vec4 data[2] = fetch_from_resource_cache_2(address);
return TextRun(data[0], data[1].xy, int(data[1].z));
vec4 data[3] = fetch_from_resource_cache_3(address);
return TextRun(data[0], data[1], data[2].xy, int(data[2].z));
}
struct Image {

Просмотреть файл

@ -19,6 +19,8 @@ flat varying vec2 vClipSign;
flat varying vec4 vEdgeDistance;
flat varying float vSDFSelect;
flat varying float vIsBorderRadiusLessThanBorderWidth;
// Border style
flat varying float vAlphaSelect;
@ -180,6 +182,8 @@ void main(void) {
edge_distances = vec4(p0 + adjusted_widths.xy,
p0 + inv_adjusted_widths.xy);
color_delta = vec2(1.0);
vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].xy,
border.widths.xy)) ? 1.0 : 0.0;
break;
}
case 1: {
@ -204,6 +208,8 @@ void main(void) {
p1.x - border.widths.z + adjusted_widths.z,
p0.y + inv_adjusted_widths.y);
color_delta = vec2(1.0, -1.0);
vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].zw,
border.widths.zy)) ? 1.0 : 0.0;
break;
}
case 2: {
@ -228,6 +234,8 @@ void main(void) {
p1.x - border.widths.z + adjusted_widths.z,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0);
vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].xy,
border.widths.zw)) ? 1.0 : 0.0;
break;
}
case 3: {
@ -252,6 +260,8 @@ void main(void) {
p0.x + inv_adjusted_widths.x,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0, 1.0);
vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].zw,
border.widths.xw)) ? 1.0 : 0.0;
break;
}
}
@ -332,7 +342,8 @@ void main(void) {
// Only apply the clip AA if inside the clip region. This is
// necessary for correctness when the border width is greater
// than the border radius.
if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
if (vIsBorderRadiusLessThanBorderWidth == 0.0 ||
all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
vec2 p = local_pos - vClipCenter;
// The coordinate system is snapped to pixel boundaries. To sample the distance,

Просмотреть файл

@ -17,7 +17,10 @@ varying vec3 vLocalPos;
#define MODE_ALPHA 0
#define MODE_SUBPX_PASS0 1
#define MODE_SUBPX_PASS1 2
#define MODE_COLOR_BITMAP 3
#define MODE_SUBPX_BG_PASS0 3
#define MODE_SUBPX_BG_PASS1 4
#define MODE_SUBPX_BG_PASS2 5
#define MODE_COLOR_BITMAP 6
void main(void) {
Primitive prim = load_primitive();
@ -59,16 +62,25 @@ void main(void) {
write_clip(vi.screen_pos, prim.clip_area);
#ifdef WR_FEATURE_SUBPX_BG_PASS1
vColor = vec4(text.color.a) * text.bg_color;
#else
switch (uMode) {
case MODE_ALPHA:
case MODE_SUBPX_PASS1:
case MODE_SUBPX_BG_PASS2:
vColor = text.color;
break;
case MODE_SUBPX_PASS0:
case MODE_SUBPX_BG_PASS0:
case MODE_COLOR_BITMAP:
vColor = vec4(text.color.a);
break;
case MODE_SUBPX_BG_PASS1:
// This should never be reached.
break;
}
#endif
vec2 texture_size = vec2(textureSize(sColor0, 0));
vec2 st0 = res.uv_rect.xy / texture_size;
@ -82,7 +94,7 @@ void main(void) {
#ifdef WR_FRAGMENT_SHADER
void main(void) {
vec3 tc = vec3(clamp(vUv.xy, vUvBorder.xy, vUvBorder.zw), vUv.z);
vec4 color = texture(sColor0, tc);
vec4 mask = texture(sColor0, tc);
float alpha = 1.0;
#ifdef WR_FEATURE_TRANSFORM
@ -90,6 +102,10 @@ void main(void) {
#endif
alpha *= do_clip();
oFragColor = color * vColor * alpha;
#ifdef WR_FEATURE_SUBPX_BG_PASS1
mask.rgb = vec3(mask.a) - mask.rgb;
#endif
oFragColor = vColor * mask * alpha;
}
#endif

Просмотреть файл

@ -230,14 +230,16 @@ impl ClipScrollNode {
self.children.push(child);
}
pub fn apply_old_scrolling_state(&mut self, new_scrolling: &ScrollingState) {
pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollingState) {
match self.node_type {
NodeType::ScrollFrame(ref mut scrolling) => {
let scroll_sensitivity = scrolling.scroll_sensitivity;
*scrolling = *new_scrolling;
let scrollable_size = scrolling.scrollable_size;
*scrolling = *old_scrolling_state;
scrolling.scroll_sensitivity = scroll_sensitivity;
scrolling.scrollable_size = scrollable_size;
}
_ if new_scrolling.offset != LayerVector2D::zero() => {
_ if old_scrolling_state.offset != LayerVector2D::zero() => {
warn!("Tried to scroll a non-scroll node.")
}
_ => {}

Просмотреть файл

@ -47,10 +47,9 @@ pub struct ClipScrollTree {
/// this ID is not valid, which is indicated by ```node``` being empty.
pub root_reference_frame_id: ClipId,
/// The topmost scrolling node that we have, which is decided by the first scrolling node
/// to be added to the tree. This is really only useful for Servo, so we should figure out
/// a good way to remove it in the future.
pub topmost_scrolling_node_id: Option<ClipId>,
/// The root scroll node which is the first child of the root reference frame.
/// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
pub topmost_scrolling_node_id: ClipId,
/// A set of pipelines which should be discarded the next time this
/// tree is drained.
@ -83,7 +82,7 @@ impl ClipScrollTree {
pending_scroll_offsets: FastHashMap::default(),
currently_scrolling_node_id: None,
root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
topmost_scrolling_node_id: None,
topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
current_new_node_item: 1,
pipelines_to_discard: FastHashSet::default(),
}
@ -96,6 +95,13 @@ impl ClipScrollTree {
self.root_reference_frame_id
}
pub fn topmost_scrolling_node_id(&self) -> ClipId {
// TODO(mrobinson): We should eventually make this impossible to misuse.
debug_assert!(!self.nodes.is_empty());
debug_assert!(self.nodes.contains_key(&self.topmost_scrolling_node_id));
self.topmost_scrolling_node_id
}
pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipId> {
let mut nodes_bouncing_back = FastHashSet::default();
for (clip_id, node) in self.nodes.iter() {
@ -135,6 +141,11 @@ impl ClipScrollTree {
})
}
pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
.unwrap_or(self.topmost_scrolling_node_id())
}
pub fn is_point_clipped_in_for_node(
&self,
point: WorldPoint,
@ -250,17 +261,11 @@ impl ClipScrollTree {
return false;
}
let topmost_scrolling_node_id = match self.topmost_scrolling_node_id {
Some(id) => id,
None => return false,
};
let scrolling_node = self.find_scrolling_node_at_point_in_node(
&cursor,
self.root_reference_frame_id()
).unwrap_or(topmost_scrolling_node_id);;
let clip_id = match (phase, scrolling_node, self.currently_scrolling_node_id) {
let clip_id = match (
phase,
self.find_scrolling_node_at_point(&cursor),
self.currently_scrolling_node_id,
) {
(ScrollEventPhase::Start, scroll_node_at_point_id, _) => {
self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
scroll_node_at_point_id
@ -278,6 +283,7 @@ impl ClipScrollTree {
(_, _, None) => return false,
};
let topmost_scrolling_node_id = self.topmost_scrolling_node_id();
let non_root_overscroll = if clip_id != topmost_scrolling_node_id {
self.nodes.get(&clip_id).unwrap().is_overscrolling()
} else {
@ -480,10 +486,6 @@ impl ClipScrollTree {
}
pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
if let NodeType::ScrollFrame(..) = node.node_type {
self.topmost_scrolling_node_id.get_or_insert(id);
}
// When the parent node is None this means we are adding the root.
match node.parent {
Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),

Просмотреть файл

@ -1913,6 +1913,18 @@ impl Device {
pub fn set_blend_mode_subpixel_pass1(&self) {
self.gl.blend_func(gl::ONE, gl::ONE);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
self.gl.blend_equation(gl::FUNC_ADD);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
self.gl.blend_equation(gl::FUNC_ADD);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
self.gl.blend_equation(gl::FUNC_ADD);
}
}
/// return (gl_internal_format, gl_format)

Просмотреть файл

@ -78,7 +78,7 @@ impl<'a> FlattenContext<'a> {
&mut self,
traversal: &mut BuiltDisplayListIter<'a>,
pipeline_id: PipelineId,
frame_size: &LayoutSize,
content_size: &LayoutSize,
) {
self.builder.push_stacking_context(
&LayerVector2D::zero(),
@ -97,11 +97,11 @@ impl<'a> FlattenContext<'a> {
// For the root pipeline, there's no need to add a full screen rectangle
// here, as it's handled by the framebuffer clear.
let clip_id = ClipId::root_reference_frame(pipeline_id);
let clip_id = ClipId::root_scroll_node(pipeline_id);
if self.scene.root_pipeline_id != Some(pipeline_id) {
if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
if let Some(bg_color) = pipeline.background_color {
let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size);
let info = LayerPrimitiveInfo::new(root_bounds);
self.builder.add_solid_rectangle(
ClipAndScrollInfo::simple(clip_id),
@ -120,14 +120,12 @@ impl<'a> FlattenContext<'a> {
let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
let info = LayerPrimitiveInfo::new(scrollbar_rect);
if let Some(node_id) = self.clip_scroll_tree.topmost_scrolling_node_id {
self.builder.add_solid_rectangle(
ClipAndScrollInfo::simple(clip_id),
&info,
&RectangleContent::Fill(DEFAULT_SCROLLBAR_COLOR),
PrimitiveFlags::Scrollbar(node_id, 4.0),
);
}
self.builder.add_solid_rectangle(
ClipAndScrollInfo::simple(clip_id),
&info,
&RectangleContent::Fill(DEFAULT_SCROLLBAR_COLOR),
PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0),
);
}
self.builder.pop_stacking_context();
@ -342,7 +340,7 @@ impl<'a> FlattenContext<'a> {
let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
let origin = reference_frame_relative_offset + bounds.origin.to_vector();
let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0);
self.builder.push_reference_frame(
let iframe_reference_frame_id = self.builder.push_reference_frame(
Some(clip_id),
pipeline_id,
&iframe_rect,
@ -352,10 +350,20 @@ impl<'a> FlattenContext<'a> {
self.clip_scroll_tree,
);
self.builder.add_scroll_frame(
ClipId::root_scroll_node(pipeline_id),
iframe_reference_frame_id,
pipeline_id,
&iframe_rect,
&pipeline.content_size,
ScrollSensitivity::ScriptAndInputEvents,
self.clip_scroll_tree,
);
self.flatten_root(
&mut pipeline.display_list.iter(),
pipeline_id,
&bounds.size,
&pipeline.content_size,
);
self.builder.pop_reference_frame();
@ -671,6 +679,7 @@ impl<'a> FlattenContext<'a> {
content,
PrimitiveFlags::None,
);
for clipped_rect in &clipped_rects {
let mut info = info.clone();
info.rect = *clipped_rect;
@ -1097,6 +1106,7 @@ impl FrameContext {
roller.builder.push_root(
root_pipeline_id,
&root_pipeline.viewport_size,
&root_pipeline.content_size,
roller.clip_scroll_tree,
);

Просмотреть файл

@ -468,10 +468,9 @@ impl FrameBuilder {
root_node.local_clip_rect = viewport_clip;
}
if let Some(clip_id) = clip_scroll_tree.topmost_scrolling_node_id {
if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&clip_id) {
root_node.local_clip_rect = viewport_clip;
}
let clip_id = clip_scroll_tree.topmost_scrolling_node_id();
if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&clip_id) {
root_node.local_clip_rect = viewport_clip;
}
}
@ -479,6 +478,7 @@ impl FrameBuilder {
&mut self,
pipeline_id: PipelineId,
viewport_size: &LayerSize,
content_size: &LayerSize,
clip_scroll_tree: &mut ClipScrollTree,
) -> ClipId {
let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
@ -493,7 +493,20 @@ impl FrameBuilder {
clip_scroll_tree,
);
clip_scroll_tree.root_reference_frame_id
let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
self.add_scroll_frame(
topmost_scrolling_node_id,
clip_scroll_tree.root_reference_frame_id,
pipeline_id,
&viewport_rect,
content_size,
ScrollSensitivity::ScriptAndInputEvents,
clip_scroll_tree,
);
topmost_scrolling_node_id
}
pub fn add_clip_node(
@ -1128,6 +1141,7 @@ impl FrameBuilder {
font.font_key,
font.size,
*color,
font.bg_color,
render_mode,
font.subpx_dir,
font.platform_options,

Просмотреть файл

@ -11,6 +11,7 @@ This is a port of Skia gamma LUT logic into Rust, used by WebRender.
#![allow(dead_code)]
use api::ColorU;
use std::cmp::max;
/// Color space responsible for converting between lumas and luminances.
#[derive(Clone, Copy, Debug, PartialEq)]
@ -295,82 +296,41 @@ impl GammaLut {
table
}
// Skia normally preblends based on what the text color is.
// If we can't do that, use Skia default colors.
pub fn preblend_default_colors_bgra(&self, pixels: &mut [u8], width: usize, height: usize) {
let preblend_color = ColorU::new(0x7f, 0x80, 0x7f, 0xff);
self.preblend_bgra(pixels, width, height, preblend_color);
}
fn replace_pixels_bgra(&self, pixels: &mut [u8], width: usize, height: usize,
table_r: &[u8; 256], table_g: &[u8; 256], table_b: &[u8; 256]) {
for y in 0..height {
let current_height = y * width * 4;
for pixel in pixels[current_height..current_height + (width * 4)].chunks_mut(4) {
pixel[0] = table_b[pixel[0] as usize];
pixel[1] = table_g[pixel[1] as usize];
pixel[2] = table_r[pixel[2] as usize];
// Don't touch alpha
}
}
}
// Mostly used by windows and GlyphRunAnalysis::GetAlphaTexture
fn replace_pixels_rgb(&self, pixels: &mut [u8], width: usize, height: usize,
table_r: &[u8; 256], table_g: &[u8; 256], table_b: &[u8; 256]) {
for y in 0..height {
let current_height = y * width * 3;
for pixel in pixels[current_height..current_height + (width * 3)].chunks_mut(3) {
pixel[0] = table_r[pixel[0] as usize];
pixel[1] = table_g[pixel[1] as usize];
pixel[2] = table_b[pixel[2] as usize];
}
}
}
// Assumes pixels are in BGRA format. Assumes pixel values are in linear space already.
pub fn preblend_bgra(&self, pixels: &mut [u8], width: usize, height: usize, color: ColorU) {
pub fn preblend(&self, pixels: &mut [u8], color: ColorU) {
let table_r = self.get_table(color.r);
let table_g = self.get_table(color.g);
let table_b = self.get_table(color.b);
self.replace_pixels_bgra(pixels, width, height, table_r, table_g, table_b);
}
// Assumes pixels are in RGB format. Assumes pixel values are in linear space already. NOTE:
// there is no alpha here.
pub fn preblend_rgb(&self, pixels: &mut [u8], width: usize, height: usize, color: ColorU) {
let table_r = self.get_table(color.r);
let table_g = self.get_table(color.g);
let table_b = self.get_table(color.b);
self.replace_pixels_rgb(pixels, width, height, table_r, table_g, table_b);
for pixel in pixels.chunks_mut(4) {
let (b, g, r) = (table_b[pixel[0] as usize], table_g[pixel[1] as usize], table_r[pixel[2] as usize]);
pixel[0] = b;
pixel[1] = g;
pixel[2] = r;
pixel[3] = max(max(b, g), r);
}
}
#[cfg(target_os="macos")]
pub fn coregraphics_convert_to_linear_bgra(&self, pixels: &mut [u8], width: usize, height: usize) {
self.replace_pixels_bgra(pixels, width, height,
&self.cg_inverse_gamma,
&self.cg_inverse_gamma,
&self.cg_inverse_gamma);
pub fn coregraphics_convert_to_linear(&self, pixels: &mut [u8]) {
for pixel in pixels.chunks_mut(4) {
pixel[0] = self.cg_inverse_gamma[pixel[0] as usize];
pixel[1] = self.cg_inverse_gamma[pixel[1] as usize];
pixel[2] = self.cg_inverse_gamma[pixel[2] as usize];
}
}
// Assumes pixels are in BGRA format. Assumes pixel values are in linear space already.
pub fn preblend_grayscale_bgra(&self, pixels: &mut [u8], width: usize, height: usize, color: ColorU) {
pub fn preblend_grayscale(&self, pixels: &mut [u8], color: ColorU) {
let table_g = self.get_table(color.g);
for y in 0..height {
let current_height = y * width * 4;
for pixel in pixels[current_height..current_height + (width * 4)].chunks_mut(4) {
let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]);
pixel[0] = table_g[luminance as usize];
pixel[1] = table_g[luminance as usize];
pixel[2] = table_g[luminance as usize];
pixel[3] = table_g[luminance as usize];
}
for pixel in pixels.chunks_mut(4) {
let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]);
let alpha = table_g[luminance as usize];
pixel[0] = alpha;
pixel[1] = alpha;
pixel[2] = alpha;
pixel[3] = alpha;
}
}
@ -378,7 +338,6 @@ impl GammaLut {
#[cfg(test)]
mod tests {
use std::cmp;
use super::*;
fn over(dst: u32, src: u32, alpha: u32) -> u32 {
@ -414,7 +373,7 @@ mod tests {
let true_result = ((overf(lin_dst, lin_src, alpha as f32) / 255.).powf(1. / g) * 255.) as u32;
let diff = absdiff(preblend_result, true_result);
//println!("{} -- {} {} = {}", alpha, preblend_result, true_result, diff);
max_diff = cmp::max(max_diff, diff);
max_diff = max(max_diff, diff);
}
//println!("{} {} max {}", src, dst, max_diff);

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#[cfg(test)]
use api::{ColorF, IdNamespace, LayoutPoint, SubpixelDirection};
use api::{ColorF, ColorU, IdNamespace, LayoutPoint, SubpixelDirection};
use api::{DevicePoint, DeviceUintSize, FontInstance, FontRenderMode};
use api::{FontKey, FontTemplate, GlyphDimensions, GlyphKey};
use api::{ImageData, ImageDescriptor, ImageFormat};
@ -447,6 +447,7 @@ fn raterize_200_glyphs() {
font_key,
Au::from_px(32),
ColorF::new(0.0, 0.0, 0.0, 1.0),
ColorU::new(0, 0, 0, 0),
FontRenderMode::Subpixel,
SubpixelDirection::Horizontal,
None,

Просмотреть файл

@ -371,20 +371,16 @@ impl FontContext {
fn gamma_correct_pixels(
&self,
pixels: &mut Vec<u8>,
width: usize,
height: usize,
render_mode: FontRenderMode,
color: ColorU,
) {
// Then convert back to gamma corrected values.
match render_mode {
FontRenderMode::Alpha => {
self.gamma_lut
.preblend_grayscale_bgra(pixels, width, height, color);
self.gamma_lut.preblend_grayscale(pixels, color);
}
FontRenderMode::Subpixel => {
self.gamma_lut
.preblend_bgra(pixels, width, height, color);
self.gamma_lut.preblend(pixels, color);
}
_ => {} // Again, give mono untouched since only the alpha matters.
}
@ -595,35 +591,28 @@ impl FontContext {
// We explicitly do not do this for grayscale AA ("Alpha without
// smoothing" or Mono) because those rendering modes are not
// gamma-aware in CoreGraphics.
self.gamma_lut.coregraphics_convert_to_linear_bgra(
self.gamma_lut.coregraphics_convert_to_linear(
&mut rasterized_pixels,
metrics.rasterized_width as usize,
metrics.rasterized_height as usize,
);
}
for i in 0 .. metrics.rasterized_height {
let current_height = (i * metrics.rasterized_width * 4) as usize;
let end_row = current_height + (metrics.rasterized_width as usize * 4);
for pixel in rasterized_pixels.chunks_mut(4) {
if invert {
pixel[0] = 255 - pixel[0];
pixel[1] = 255 - pixel[1];
pixel[2] = 255 - pixel[2];
}
for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
if invert {
pixel[0] = 255 - pixel[0];
pixel[1] = 255 - pixel[1];
pixel[2] = 255 - pixel[2];
}
// Set alpha to the value of the green channel. For grayscale
// text, all three channels have the same value anyway.
// For subpixel text, the mask's alpha only makes a difference
// when computing the destination alpha on destination pixels
// that are not completely opaque. Picking an alpha value
// that's somehow based on the mask at least ensures that text
// blending doesn't modify the destination alpha on pixels where
// the mask is entirely zero.
pixel[3] = pixel[1];
} // end row
} // end height
// Set alpha to the value of the green channel. For grayscale
// text, all three channels have the same value anyway.
// For subpixel text, the mask's alpha only makes a difference
// when computing the destination alpha on destination pixels
// that are not completely opaque. Picking an alpha value
// that's somehow based on the mask at least ensures that text
// blending doesn't modify the destination alpha on pixels where
// the mask is entirely zero.
pixel[3] = pixel[1];
}
if smooth {
// Convert back from linear space into device space, and perform
@ -632,8 +621,6 @@ impl FontContext {
// into grayscale AA.
self.gamma_correct_pixels(
&mut rasterized_pixels,
metrics.rasterized_width as usize,
metrics.rasterized_height as usize,
font.render_mode,
font.color,
);

Просмотреть файл

@ -11,7 +11,7 @@ use freetype::freetype::{FT_BBox, FT_Outline_Translate, FT_Pixel_Mode, FT_Render
use freetype::freetype::{FT_Done_Face, FT_Error, FT_Get_Char_Index, FT_Int32};
use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos};
use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt};
use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Memory_Face};
use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face};
use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
@ -21,6 +21,8 @@ use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES, FT_Err
use glyph_rasterizer::{GlyphFormat, RasterizedGlyph};
use internal_types::FastHashMap;
use std::{cmp, mem, ptr, slice};
use std::cmp::max;
use std::ffi::CString;
use std::sync::Arc;
// These constants are not present in the freetype
@ -36,7 +38,7 @@ struct Face {
face: FT_Face,
// Raw byte data has to live until the font is deleted, according to
// https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_New_Memory_Face
_bytes: Arc<Vec<u8>>,
_bytes: Option<Arc<Vec<u8>>>,
}
pub struct FontContext {
@ -103,7 +105,7 @@ impl FontContext {
*font_key,
Face {
face,
_bytes: bytes,
_bytes: Some(bytes),
},
);
} else {
@ -112,8 +114,30 @@ impl FontContext {
}
}
pub fn add_native_font(&mut self, _font_key: &FontKey, _native_font_handle: NativeFontHandle) {
panic!("TODO: Not supported on Linux");
pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
if !self.faces.contains_key(&font_key) {
let mut face: FT_Face = ptr::null_mut();
let pathname = CString::new(native_font_handle.pathname).unwrap();
let result = unsafe {
FT_New_Face(
self.lib,
pathname.as_ptr(),
native_font_handle.index as FT_Long,
&mut face,
)
};
if result.succeeded() && !face.is_null() {
self.faces.insert(
*font_key,
Face {
face,
_bytes: None,
},
);
} else {
println!("WARN: webrender failed to load font {:?}", font_key);
}
}
}
pub fn delete_font(&mut self, font_key: &FontKey) {
@ -554,45 +578,32 @@ impl FontContext {
}
}
FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
if subpixel_bgr {
while dest < row_end {
final_buffer[dest + 0] = unsafe { *src };
final_buffer[dest + 1] = unsafe { *src.offset(1) };
final_buffer[dest + 2] = unsafe { *src.offset(2) };
final_buffer[dest + 3] = 0xff;
src = unsafe { src.offset(3) };
dest += 4;
}
} else {
while dest < row_end {
final_buffer[dest + 2] = unsafe { *src };
final_buffer[dest + 1] = unsafe { *src.offset(1) };
final_buffer[dest + 0] = unsafe { *src.offset(2) };
final_buffer[dest + 3] = 0xff;
src = unsafe { src.offset(3) };
dest += 4;
while dest < row_end {
let (mut r, g, mut b) = unsafe { (*src, *src.offset(1), *src.offset(2)) };
if subpixel_bgr {
mem::swap(&mut r, &mut b);
}
final_buffer[dest + 0] = b;
final_buffer[dest + 1] = g;
final_buffer[dest + 2] = r;
final_buffer[dest + 3] = max(max(b, g), r);
src = unsafe { src.offset(3) };
dest += 4;
}
}
FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
if subpixel_bgr {
while dest < row_end {
final_buffer[dest + 0] = unsafe { *src };
final_buffer[dest + 1] = unsafe { *src.offset(bitmap.pitch as isize) };
final_buffer[dest + 2] = unsafe { *src.offset((2 * bitmap.pitch) as isize) };
final_buffer[dest + 3] = 0xff;
src = unsafe { src.offset(1) };
dest += 4;
}
} else {
while dest < row_end {
final_buffer[dest + 2] = unsafe { *src };
final_buffer[dest + 1] = unsafe { *src.offset(bitmap.pitch as isize) };
final_buffer[dest + 0] = unsafe { *src.offset((2 * bitmap.pitch) as isize) };
final_buffer[dest + 3] = 0xff;
src = unsafe { src.offset(1) };
dest += 4;
while dest < row_end {
let (mut r, g, mut b) =
unsafe { (*src, *src.offset(bitmap.pitch as isize), *src.offset((2 * bitmap.pitch) as isize)) };
if subpixel_bgr {
mem::swap(&mut r, &mut b);
}
final_buffer[dest + 0] = b;
final_buffer[dest + 1] = g;
final_buffer[dest + 2] = r;
final_buffer[dest + 3] = max(max(b, g), r);
src = unsafe { src.offset(1) };
dest += 4;
}
src_row = unsafe { src_row.offset((2 * bitmap.pitch) as isize) };
}
@ -605,6 +616,7 @@ impl FontContext {
_ => panic!("Unsupported {:?}", pixel_mode),
}
src_row = unsafe { src_row.offset(bitmap.pitch as isize) };
dest = row_end;
}
Some(RasterizedGlyph {

Просмотреть файл

@ -253,46 +253,46 @@ impl FontContext {
})
}
// DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
// TODO: Decide whether all fonts should return RGB or BGR
fn convert_to_rgba(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
// DWrite ClearType gives us values in RGB, but WR expects BGRA.
fn convert_to_bgra(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
match render_mode {
FontRenderMode::Bitmap => {
unreachable!("TODO: bitmap fonts");
}
FontRenderMode::Mono => {
let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
let mut bgra_pixels: Vec<u8> = vec![0; pixels.len() * 4];
for i in 0 .. pixels.len() {
rgba_pixels[i * 4 + 0] = pixels[i];
rgba_pixels[i * 4 + 1] = pixels[i];
rgba_pixels[i * 4 + 2] = pixels[i];
rgba_pixels[i * 4 + 3] = pixels[i];
let alpha = pixels[i];
bgra_pixels[i * 4 + 0] = alpha;
bgra_pixels[i * 4 + 1] = alpha;
bgra_pixels[i * 4 + 2] = alpha;
bgra_pixels[i * 4 + 3] = alpha;
}
rgba_pixels
bgra_pixels
}
FontRenderMode::Alpha => {
let length = pixels.len() / 3;
let mut rgba_pixels: Vec<u8> = vec![0; length * 4];
let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
for i in 0 .. length {
// Only take the G channel, as its closest to D2D
let alpha = pixels[i * 3 + 1] as u8;
rgba_pixels[i * 4 + 0] = alpha;
rgba_pixels[i * 4 + 1] = alpha;
rgba_pixels[i * 4 + 2] = alpha;
rgba_pixels[i * 4 + 3] = alpha;
bgra_pixels[i * 4 + 0] = alpha;
bgra_pixels[i * 4 + 1] = alpha;
bgra_pixels[i * 4 + 2] = alpha;
bgra_pixels[i * 4 + 3] = alpha;
}
rgba_pixels
bgra_pixels
}
FontRenderMode::Subpixel => {
let length = pixels.len() / 3;
let mut rgba_pixels: Vec<u8> = vec![0; length * 4];
let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
for i in 0 .. length {
rgba_pixels[i * 4 + 0] = pixels[i * 3 + 0];
rgba_pixels[i * 4 + 1] = pixels[i * 3 + 1];
rgba_pixels[i * 4 + 2] = pixels[i * 3 + 2];
rgba_pixels[i * 4 + 3] = 0xff;
bgra_pixels[i * 4 + 0] = pixels[i * 3 + 0];
bgra_pixels[i * 4 + 1] = pixels[i * 3 + 1];
bgra_pixels[i * 4 + 2] = pixels[i * 3 + 2];
bgra_pixels[i * 4 + 3] = 0xff;
}
rgba_pixels
bgra_pixels
}
}
}
@ -328,8 +328,8 @@ impl FontContext {
let texture_type = dwrite_texture_type(font.render_mode);
let bounds = analysis.get_alpha_texture_bounds(texture_type);
let width = (bounds.right - bounds.left) as usize;
let height = (bounds.bottom - bounds.top) as usize;
let width = (bounds.right - bounds.left) as u32;
let height = (bounds.bottom - bounds.top) as u32;
// Alpha texture bounds can sometimes return an empty rect
// Such as for spaces
@ -337,7 +337,8 @@ impl FontContext {
return None;
}
let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
let pixels = analysis.create_alpha_texture(texture_type, bounds);
let mut bgra_pixels = self.convert_to_bgra(&pixels, font.render_mode);
match font.render_mode {
FontRenderMode::Mono | FontRenderMode::Bitmap => {}
@ -351,25 +352,18 @@ impl FontContext {
None => &self.gamma_lut,
};
lut_correction.preblend_rgb(
&mut pixels,
width,
height,
font.color,
);
lut_correction.preblend(&mut bgra_pixels, font.color);
}
}
let rgba_pixels = self.convert_to_rgba(&mut pixels, font.render_mode);
Some(RasterizedGlyph {
left: bounds.left as f32,
top: -bounds.top as f32,
width: width as u32,
height: height as u32,
width,
height,
scale: 1.0,
format: GlyphFormat::from(font.render_mode),
bytes: rgba_pixels,
bytes: bgra_pixels,
})
}
}

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect};
use api::{DevicePoint, ExtendMode, FontInstance, FontRenderMode, GlyphInstance, GlyphKey};
use api::{DevicePoint, ExtendMode, FontInstance, GlyphInstance, GlyphKey};
use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect};
use api::{ClipMode, LayerSize, LayerVector2D, LineOrientation, LineStyle};
use api::{TileOffset, YuvColorSpace, YuvFormat};
@ -554,26 +554,10 @@ pub struct TextRunPrimitiveCpu {
pub glyph_gpu_blocks: Vec<GpuBlockData>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TextRunMode {
Normal,
Shadow,
}
impl TextRunPrimitiveCpu {
pub fn get_font(&self,
run_mode: TextRunMode,
device_pixel_ratio: f32,
) -> FontInstance {
pub fn get_font(&self, device_pixel_ratio: f32) -> FontInstance {
let mut font = self.font.clone();
match run_mode {
TextRunMode::Normal => {}
TextRunMode::Shadow => {
// Shadows never use subpixel AA, but need to respect the alpha/mono flag
// for reftests.
font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
}
};
font.size = font.size.scale_by(device_pixel_ratio);
font
}
@ -583,10 +567,9 @@ impl TextRunPrimitiveCpu {
resource_cache: &mut ResourceCache,
device_pixel_ratio: f32,
display_list: &BuiltDisplayList,
run_mode: TextRunMode,
gpu_cache: &mut GpuCache,
) {
let font = self.get_font(run_mode, device_pixel_ratio);
let font = self.get_font(device_pixel_ratio);
// Cache the glyph positions, if not in the cache already.
// TODO(gw): In the future, remove `glyph_instances`
@ -628,6 +611,7 @@ impl TextRunPrimitiveCpu {
fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
request.push(ColorF::from(self.font.color).premultiplied());
request.push(ColorF::from(self.font.bg_color));
request.push([
self.offset.x,
self.offset.y,
@ -1097,7 +1081,6 @@ impl PrimitiveStore {
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
render_tasks: &mut RenderTaskTree,
text_run_mode: TextRunMode,
) {
let metadata = &mut self.cpu_metadata[prim_index.0];
match metadata.prim_kind {
@ -1116,7 +1099,6 @@ impl PrimitiveStore {
resource_cache,
prim_context.device_pixel_ratio,
prim_context.display_list,
text_run_mode,
gpu_cache,
);
}
@ -1357,7 +1339,6 @@ impl PrimitiveStore {
resource_cache,
gpu_cache,
render_tasks,
TextRunMode::Shadow,
);
}
}
@ -1380,7 +1361,6 @@ impl PrimitiveStore {
resource_cache,
gpu_cache,
render_tasks,
TextRunMode::Normal,
);
Some(geometry)

Просмотреть файл

@ -223,7 +223,10 @@ enum TextShaderMode {
Alpha = 0,
SubpixelPass0 = 1,
SubpixelPass1 = 2,
ColorBitmap = 3,
SubpixelWithBgColorPass0 = 3,
SubpixelWithBgColorPass1 = 4,
SubpixelWithBgColorPass2 = 5,
ColorBitmap = 6,
}
impl Into<ShaderMode> for TextShaderMode {
@ -638,6 +641,7 @@ pub enum BlendMode {
PremultipliedAlpha,
PremultipliedDestOut,
Subpixel,
SubpixelWithBgColor,
}
// Tracks the state of each row in the GPU cache texture.
@ -1009,7 +1013,8 @@ impl BrushShader {
BlendMode::Alpha |
BlendMode::PremultipliedAlpha |
BlendMode::PremultipliedDestOut |
BlendMode::Subpixel => {
BlendMode::Subpixel |
BlendMode::SubpixelWithBgColor => {
self.alpha.bind(device, projection, mode, renderer_errors)
}
}
@ -1212,6 +1217,7 @@ pub struct Renderer {
ps_rectangle: PrimitiveShader,
ps_rectangle_clip: PrimitiveShader,
ps_text_run: PrimitiveShader,
ps_text_run_subpx_bg_pass1: PrimitiveShader,
ps_image: Vec<Option<PrimitiveShader>>,
ps_yuv_image: Vec<Option<PrimitiveShader>>,
ps_border_corner: PrimitiveShader,
@ -1481,6 +1487,13 @@ impl Renderer {
options.precache_shaders)
};
let ps_text_run_subpx_bg_pass1 = try!{
PrimitiveShader::new("ps_text_run",
&mut device,
&["SUBPX_BG_PASS1"],
options.precache_shaders)
};
// All image configuration.
let mut image_features = Vec::new();
let mut ps_image: Vec<Option<PrimitiveShader>> = Vec::new();
@ -1824,6 +1837,7 @@ impl Renderer {
ps_rectangle,
ps_rectangle_clip,
ps_text_run,
ps_text_run_subpx_bg_pass1,
ps_image,
ps_yuv_image,
ps_border_corner,
@ -2478,7 +2492,8 @@ impl Renderer {
BlendMode::Alpha |
BlendMode::PremultipliedAlpha |
BlendMode::PremultipliedDestOut |
BlendMode::Subpixel => true,
BlendMode::Subpixel |
BlendMode::SubpixelWithBgColor => true,
BlendMode::None => false,
}
);
@ -2822,6 +2837,7 @@ impl Renderer {
BlendMode::PremultipliedAlpha => ColorF::new(0.0, 0.3, 0.7, 1.0),
BlendMode::PremultipliedDestOut => ColorF::new(0.6, 0.2, 0.0, 1.0),
BlendMode::Subpixel => ColorF::new(0.5, 0.0, 0.4, 1.0),
BlendMode::SubpixelWithBgColor => ColorF::new(0.6, 0.0, 0.5, 1.0),
}.into();
for item_rect in &batch.item_rects {
self.debug.add_rect(item_rect, color);
@ -2897,6 +2913,58 @@ impl Renderer {
self.device
.draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
}
BlendMode::SubpixelWithBgColor => {
// Using the three pass "component alpha with font smoothing
// background color" rendering technique:
//
// /webrender/doc/text-rendering.md
//
self.device.set_blend_mode_subpixel_with_bg_color_pass0();
self.ps_text_run.bind(
&mut self.device,
transform_kind,
projection,
TextShaderMode::SubpixelWithBgColorPass0,
&mut self.renderer_errors,
);
self.draw_instanced_batch(
&batch.instances,
VertexArrayKind::Primitive,
&batch.key.textures
);
self.device.set_blend_mode_subpixel_with_bg_color_pass1();
self.ps_text_run_subpx_bg_pass1.bind(
&mut self.device,
transform_kind,
projection,
TextShaderMode::SubpixelWithBgColorPass1,
&mut self.renderer_errors,
);
// When drawing the 2nd and 3rd passes, we know that the VAO, textures etc
// are all set up from the previous draw_instanced_batch call,
// so just issue a draw call here to avoid re-uploading the
// instances and re-binding textures etc.
self.device
.draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
self.device.set_blend_mode_subpixel_with_bg_color_pass2();
self.ps_text_run.bind(
&mut self.device,
transform_kind,
projection,
TextShaderMode::SubpixelWithBgColorPass2,
&mut self.renderer_errors,
);
self.device
.draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
}
BlendMode::Alpha | BlendMode::PremultipliedDestOut | BlendMode::None => {
unreachable!("bug: bad blend mode for text");
}
@ -2923,7 +2991,7 @@ impl Renderer {
self.device.set_blend(true);
self.device.set_blend_mode_premultiplied_dest_out();
}
BlendMode::Subpixel => {
BlendMode::Subpixel | BlendMode::SubpixelWithBgColor => {
unreachable!("bug: subpx text handled earlier");
}
}

Просмотреть файл

@ -351,6 +351,7 @@ impl ResourceCache {
render_mode,
subpx_dir,
synthetic_italics,
bg_color,
..
} = options.unwrap_or_default();
assert!(render_mode != FontRenderMode::Bitmap);
@ -358,6 +359,7 @@ impl ResourceCache {
font_key,
glyph_size,
ColorF::new(0.0, 0.0, 0.0, 1.0),
bg_color,
render_mode,
subpx_dir,
platform_options,

Просмотреть файл

@ -20,7 +20,7 @@ use internal_types::{FastHashMap, SourceTexture};
use internal_types::BatchTextures;
use picture::PictureKind;
use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, RectangleContent, TextRunMode};
use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, RectangleContent};
use profiler::FrameProfileCounters;
use render_task::{AlphaRenderItem, ClipWorkItem, MaskGeometryKind, MaskSegment};
use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind};
@ -57,9 +57,13 @@ impl AlphaBatchHelpers for PrimitiveStore {
match metadata.prim_kind {
PrimitiveKind::TextRun => {
let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
match text_run_cpu.font.render_mode {
FontRenderMode::Subpixel => BlendMode::Subpixel,
let font = &self.cpu_text_runs[metadata.cpu_prim_index.0].font;
match font.render_mode {
FontRenderMode::Subpixel => if font.bg_color.a != 0 {
BlendMode::SubpixelWithBgColor
} else {
BlendMode::Subpixel
},
FontRenderMode::Alpha |
FontRenderMode::Mono |
FontRenderMode::Bitmap => BlendMode::PremultipliedAlpha,
@ -277,7 +281,8 @@ impl BatchList {
match key.blend_mode {
BlendMode::None => self.opaque_batch_list.get_suitable_batch(key),
BlendMode::Alpha | BlendMode::PremultipliedAlpha |
BlendMode::PremultipliedDestOut | BlendMode::Subpixel => {
BlendMode::PremultipliedDestOut | BlendMode::Subpixel |
BlendMode::SubpixelWithBgColor => {
self.alpha_batch_list
.get_suitable_batch(key, item_bounding_rect)
}
@ -561,7 +566,7 @@ impl AlphaRenderItem {
let text_cpu =
&ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
let font = text_cpu.get_font(TextRunMode::Normal, ctx.device_pixel_ratio);
let font = text_cpu.get_font(ctx.device_pixel_ratio);
ctx.resource_cache.fetch_glyphs(
font,
@ -1236,7 +1241,7 @@ impl RenderTarget for ColorRenderTarget {
[sub_metadata.cpu_prim_index.0];
let text_run_cache_prims = &mut self.text_run_cache_prims;
let font = text.get_font(TextRunMode::Shadow, ctx.device_pixel_ratio);
let font = text.get_font(ctx.device_pixel_ratio);
ctx.resource_cache.fetch_glyphs(
font,

Просмотреть файл

@ -668,7 +668,12 @@ impl RenderApi {
);
}
/// Does a hit test as the given point
/// Does a hit test on display items in the specified document, at the given
/// point. If a pipeline_id is specified, it is used to further restrict the
/// hit results so that only items inside that pipeline are matched. If the
/// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit
/// results will contain all display items that match, ordered from front
/// to back.
pub fn hit_test(&self,
document_id: DocumentId,
pipeline_id: Option<PipelineId>,

Просмотреть файл

@ -689,15 +689,19 @@ pub enum ClipId {
}
impl ClipId {
pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
ClipId::Clip(0, pipeline_id)
}
pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
ClipId::DynamicallyAddedNode(0, pipeline_id)
}
pub fn new(id: u64, pipeline_id: PipelineId) -> ClipId {
// We do this because it is very easy to accidentally create something that
// seems like the root node, but isn't one.
// We do this because it is very easy to create accidentally create something that
// seems like a root scroll node, but isn't one.
if id == 0 {
return ClipId::root_reference_frame(pipeline_id);
return ClipId::root_scroll_node(pipeline_id);
}
ClipId::ClipExternalId(id, pipeline_id)
@ -718,9 +722,9 @@ impl ClipId {
}
}
pub fn is_root(&self) -> bool {
pub fn is_root_scroll_node(&self) -> bool {
match *self {
ClipId::DynamicallyAddedNode(0, _) => true,
ClipId::Clip(0, _) => true,
_ => false,
}
}

Просмотреть файл

@ -24,8 +24,8 @@ use std::slice;
use time::precise_time_ns;
// We don't want to push a long text-run. If a text-run is too long, split it into several parts.
// Please check the renderer::MAX_VERTEX_TEXTURE_WIDTH for the detail.
pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
// This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_PRIM_HEADER - VECS_PER_TEXT_RUN) * 2
pub const MAX_TEXT_RUN_LENGTH: usize = 2038;
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@ -657,7 +657,7 @@ impl DisplayListBuilder {
data: Vec::with_capacity(capacity),
pipeline_id,
clip_stack: vec![
ClipAndScrollInfo::simple(ClipId::root_reference_frame(pipeline_id)),
ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
],
next_clip_id: FIRST_CLIP_ID,
builder_start_time: start_time,

Просмотреть файл

@ -51,11 +51,12 @@ impl<'de> Deserialize<'de> for NativeFontHandle {
}
}
/// Native fonts are not used on Linux; all fonts are raw.
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
#[cfg_attr(not(any(target_os = "macos", target_os = "windows")),
derive(Clone, Serialize, Deserialize))]
pub struct NativeFontHandle;
#[derive(Clone, Serialize, Deserialize)]
pub struct NativeFontHandle {
pub pathname: String,
pub index: u32,
}
#[cfg(target_os = "windows")]
pub type NativeFontHandle = FontDescriptor;
@ -209,6 +210,10 @@ pub struct FontInstanceOptions {
pub render_mode: FontRenderMode,
pub subpx_dir: SubpixelDirection,
pub synthetic_italics: bool,
/// When bg_color.a is != 0 and render_mode is FontRenderMode::Subpixel,
/// the text will be rendered with bg_color.r/g/b as an opaque estimated
/// background color.
pub bg_color: ColorU,
}
impl Default for FontInstanceOptions {
@ -217,6 +222,7 @@ impl Default for FontInstanceOptions {
render_mode: FontRenderMode::Subpixel,
subpx_dir: SubpixelDirection::Horizontal,
synthetic_italics: false,
bg_color: ColorU::new(0, 0, 0, 0),
}
}
}
@ -313,6 +319,7 @@ pub struct FontInstance {
// or something similar to that.
pub size: Au,
pub color: ColorU,
pub bg_color: ColorU,
pub render_mode: FontRenderMode,
pub subpx_dir: SubpixelDirection,
pub platform_options: Option<FontInstancePlatformOptions>,
@ -325,6 +332,7 @@ impl FontInstance {
font_key: FontKey,
size: Au,
color: ColorF,
bg_color: ColorU,
render_mode: FontRenderMode,
subpx_dir: SubpixelDirection,
platform_options: Option<FontInstancePlatformOptions>,
@ -335,6 +343,7 @@ impl FontInstance {
font_key,
size,
color: color.into(),
bg_color,
render_mode,
subpx_dir,
platform_options,