-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] SDF text rendering #36171
Conversation
| //------------------------------------------------------------------------------ | ||
| /// @brief Whether the glyph is a path or a bitmap. | ||
| /// | ||
| Type type = Type::kPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be incorporated into equality/hash right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I... guess? This is specific to the glyph ID though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah....I dunno. As long as we never have glyph collisions its fine
| /// @brief Describes how the glyphs are represented in the texture. | ||
| enum class Type { | ||
| //-------------------------------------------------------------------------- | ||
| /// The glyphs are represented at a fixed size in an 8-bit grayscale texture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW there are SDF algorithms that use all of the color channels for fewer artifacts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but in talking with Chinmay about this he's concerned they're slow and more meant for offline processing.
Maybe we could find a compute based algorithm that would be fast... in which case we'd update this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd probably add another enumeration for that too, something like kMultiChannelSignedDistanceField
|
|
||
| bool TextRun::AddGlyph(Glyph glyph, Point position) { | ||
| glyphs_.emplace_back(GlyphPosition{glyph, position}); | ||
| has_color_ |= glyph.type == Glyph::Type::kBitmap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its not that they're always bitmaps though. The COLR fonts can be drawn as a path too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I rename this to something like is_bitmap or something then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the underlying is_color is being used to detect both bitmaps and COLR fonts right now though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there was a skia parameter that returned whether or not something was a bitmap but I couldn't figure out how it was initialized, and seemed to always be false even for emoji
|
This definitely has some issues still, but I think it's good enough to review and land to star tmaking incremental improvements. Current issues:
|
| in vec2 v_unit_vertex; | ||
| in vec2 v_atlas_position; | ||
| in vec2 v_atlas_glyph_size; | ||
| in float v_color_glyph; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one isn't used. I think we can't render full color glyphs or bitmaps with SDF anyway so I'd drop it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd rather just leave this to avoid replicating a bunch of code on the C++ side for now.
Co-authored-by: Jonah Williams <jonahwilliams@google.com>
testing/testing.gni
Outdated
| # Unit tests targets are only enabled for host machines and Fuchsia right now | ||
| declare_args() { | ||
| enable_unittests = current_toolchain == host_toolchain || is_fuchsia | ||
| enable_unittests = current_toolchain == host_toolchain || is_fuchsia || true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to stage this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No
|
|
||
| if (auto atlas = std::get_if<std::shared_ptr<GlyphAtlas>>(&atlas_)) { | ||
| return *atlas; | ||
| FML_DCHECK(lazy_atlas_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the assertion as well as the conditional is redundant. I suggest removing the assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditional is defensive - the assert is so that we don't get here in a test and return null, which would fail to render text.
| mat4 mvp; | ||
| } frame_info; | ||
|
|
||
| in vec2 unit_vertex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be shared with the non-SDF glyph atlas implementation. You can put the common stuff in its own header and #include that here and in the other spot too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I am surprised so much is shared between the two strategies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very similar. We might be able to simplify it if we move SDF to using a fixed glyph size, but it's probably not worth it. The differences are really in the fragment shader.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this to transforms.glsl
| while (auto frame = frame_iterator()) { | ||
| for (const auto& run : frame->GetRuns()) { | ||
| auto font = run.GetFont(); | ||
| // switch (type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't a consistent font size desirable for an SDF based strategy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I saw your clarifying remark. Let's cross reference a bug here instead of leaving the code commented out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flutter/flutter#112016, adding TODO
| /// Compute signed-distance field for an 8-bpp grayscale image (values greater | ||
| /// than 127 are considered "on") For details of this algorithm, see "The 'dead | ||
| /// reckoning' signed distance transform" [Grevera 2004] | ||
| static void ConvertBitmapToSignedDistanceField(uint8_t* pixels, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its going to take me a bit to walk through this (and we should totally not block landing this because of it). But, can we fixup the style to match the style guide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean the comment bit? I tried to fix up the variable names.
Perhaps you mean the in-out pointer parameter? Doing this in-place avoids an extra allocation the size of the texture though. Maybe there's a more style-guide friendly way of doing that?
| for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) { | ||
| TextRun text_run(ToFont(run.font(), scale)); | ||
|
|
||
| // TODO(jonahwilliams): ask Skia for a public API to look this up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we file a bug for this and cross reference it here? We can followup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added bug to comment.
chinmaygarde
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nits. Let's file issues for the remaining action items for followup. This is a great start and let's land iterate on it ASAP.
| cmd.pipeline = | ||
| renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity)); | ||
| cmd.stencil_reference = entity.GetStencilDepth(); | ||
| template <class VS_, class FS_> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The template type param can just be the pipeline type itself rather than its shaders (see the advanced blends in BlendFilter for an example of this).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, doen
| float sample_distance = texture(glyph_atlas_sampler, v_unit_vertex * scale_perspective + offset).a; | ||
| // // Use local automatic gradients to find anti-aliased anisotropic edge width, cf. Gustavson 2012 | ||
| float edge_width = length(vec2(dFdx(sample_distance), dFdy(sample_distance))); | ||
| // // Smooth the glyph edge by interpolating across the boundary in a band with the width determined above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Double double slashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
| // // Smooth the glyph edge by interpolating across the boundary in a band with the width determined above | ||
| float insideness = smoothstep(edge_distance - edge_width, edge_distance + edge_width, sample_distance); | ||
| frag_color = frag_info.text_color * insideness; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: IIRC, I think you're using the same VScode plugins as me for shaders. Mind running this through the formatter with Cmd + Alt + L?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't seem to get the formatter working :\
| // Sample the signed-distance field to find distance from this fragment to the glyph outline | ||
| float sample_distance = texture(glyph_atlas_sampler, v_unit_vertex * scale_perspective + offset).a; | ||
| // // Use local automatic gradients to find anti-aliased anisotropic edge width, cf. Gustavson 2012 | ||
| float edge_width = length(vec2(dFdx(sample_distance), dFdy(sample_distance))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice trick -- derivatives work the same whether or not the render pass does multisample resolve (considering MSAA changes nothing about how the fragment stage runs), right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.. think so. I heavily borrowed from the Metal by Example shader for SDF text rendering.
| // // Use local automatic gradients to find anti-aliased anisotropic edge width, cf. Gustavson 2012 | ||
| float edge_width = length(vec2(dFdx(sample_distance), dFdy(sample_distance))); | ||
| // // Smooth the glyph edge by interpolating across the boundary in a band with the width determined above | ||
| float insideness = smoothstep(edge_distance - edge_width, edge_distance + edge_width, sample_distance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are you planning to address non-antialiased drawing? Given the template solution, It seems like binding an extra float uniform for all atlas implementations wouldn't be so bad even if unused in some of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I'm not married to the template solution, but it saved me from copy/pasting a bunch of code that was nearly identical. There's already a branch in there based on the type of glyph atlas, we could add another branch for SDF if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh except that branch doesn't work lol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fixed the branching using std::is_same, and we should be able to do that for other parameters we need here ... although at some point it may make sense to stop using templates and just replicate some code or create additional inline helper functions or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual metal by example drawing seemed to handling some AA here in a way that didn't quite work, it'd probably be worth revisiting.
|
Marking this for submission, realizing it's not quite complete and will need some further work. |
No description provided.