Update DisplayList rendering unittests for new SufaceProvider API and to be able to test Impeller#185820
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the display list testing framework to unify snapshotting capabilities across Skia and Impeller backends. Key changes include adding SnapshotToPixelData and SnapshotToImage to the DlSurfaceInstance interface, implementing backend-specific pixel data wrappers, and updating PlaygroundSwitches handling. Review feedback identifies an off-by-one error in Impeller bounds checking, a significant memory over-allocation in Skia pixel data, and potential null pointer dereferences. It also highlights an inconsistency where the Impeller implementation returns a live texture instead of a static snapshot, which could lead to flaky tests.
gaaclarke
left a comment
There was a problem hiding this comment.
Mostly looks good. I'm confused what this change is doing, can you update the description please? The description talks about what is being removed clearly.
Please make smaller PRs.
| playground_ = MakePlayground(impeller::PlaygroundBackend::kMetalSDF); | ||
| impeller::PlaygroundSwitches switches; | ||
| switches.enable_wide_gamut = true; | ||
| switches.flags.use_sdfs = true; |
There was a problem hiding this comment.
This is redundant information, can't we just infer it from the PlaygroundBackend?
There was a problem hiding this comment.
The use_sdfs is set when you ask for MetalSDF, but it isn't really mentioned by the method which takes a set of switches as an argument so the chain of responsibility for how the switches is set up is not really explicit.
| if (pixels_.get()) { | ||
| pixmap_.reset(info, pixels_.get(), info.minRowBytes()); | ||
| if (raster_image->readPixels(pixmap_, 0, 0)) { | ||
| is_valid_ = true; |
There was a problem hiding this comment.
I would like it if we could leave the "IsValid" pattern behind and use absl::StatusOr with factories instead. Nothing good can come from constructing invalid objects.
There was a problem hiding this comment.
This would be really difficult to untangle. The main issue is that SkPixmap is built around raw pointers and so we need to manage their data storage specially. I could try to work around that and do something with raw pointers, but the Skia versions of these backends has a limited future. Some of the tests could be lifted into the calling method, but in the end, we're going to need an IsValid to know if the pixel readback into the private Pixmap field succeeded.
Also, IsValid is private API. This class is private to this file and the method is not exposed through any public API. It is only for the calling internal method to know if the Skia APIs succeeded in the constructor. I agree that public non-IsValid objects are bad, but these aren't public (unless they are valid).
There was a problem hiding this comment.
Yea, trying to fix it everywhere is out of scope of this PR. But since we are editing this class we can do the right thing here by introducing the factory method and always returning true for IsValid().
There was a problem hiding this comment.
I'm not sure I was clear. There is no "everywhere" here, IsValid doesn't escape this file and is only used in one place. The work that can stump validity has to be done in a constructor, not a factory. So, even if I hide this behind a factory, the constructor needs to have to have a way to return a failure mode. And if it does so, does it matter if that validity is fielded by a factory method in this class or another method in this same file?
There was a problem hiding this comment.
I might be able to get around this by constructing a Pixmap temporarily for the readback. It looks like it's just an "annotated pointer". I'd still need a good way to transfer the allocated memory from the factory to the constructor...
There was a problem hiding this comment.
Actually, switching this was quite simple.
SkPixmap is just a holder of related data. It doesn't "own" any of it, including not owning the pixel pointer. So, it can be declared, used for read back, and then just dropped on the floor. The only ownership issue is the pixel pointer which is a unique_ptr that can just be transferred into the constructor.
| size_t width() const override { return pixmap_.info().width(); } | ||
| size_t height() const override { return pixmap_.info().height(); } | ||
|
|
||
| virtual bool write(const std::string& path) const override { |
There was a problem hiding this comment.
no action required: I'd like it if we used absl::Status here too.
There was a problem hiding this comment.
Future work if you file an issue. These are just test utilities, not used in production code.
| static std::string FailureImageDirectory; | ||
| static bool SaveFailureImages; | ||
| static std::vector<std::string> FailureImageFilenames; |
There was a problem hiding this comment.
These don't match naming rules for variables.
There was a problem hiding this comment.
The rules are somewhat ambiguous because they define the naming convention for static constant members and define exceptions to that rule. These are likely better served by making the fields private and providing accessor methods for the functionality.
There was a problem hiding this comment.
The fields are now private.
|
|
||
| std::optional<DlSurfaceProvider::BackendType> DlSurfaceProvider::NameToBackend( | ||
| const std::string& name) { | ||
| #define PROCESS_BACKEND(name, BACKEND) \ |
There was a problem hiding this comment.
Does this need to be a macro? Please add a prefix to avoid colliding macros if you keep it.
There was a problem hiding this comment.
Kept the macro, renamed it.
|
|
||
| /// Read back the current contents of the surface and return it as a | ||
| /// DlPixelData structure. | ||
| virtual std::shared_ptr<DlPixelData> SnapshotToPixelData() const = 0; |
There was a problem hiding this comment.
Shouldn't this be returning a unique_ptr?
There was a problem hiding this comment.
Why? We don't necessarily need uniqueness and you can't share a unique pointer. shared_ptr seems like the best option here so that we don't limit the use of them by the caller.
There was a problem hiding this comment.
It's the opposite of that. By returning shared_ptr you are forcing users to have the semantics for multiple owners. unique_ptr's can be implicitly cast to shared_ptr if someone wants a shared_ptr.
There was a problem hiding this comment.
This means I can cache the Providers in the test harness to avoid repeated instantiations. I'm not sure if that will make any performance improvement, but it couldn't hurt. Minimally it saves us from having to allocate the primary surface over and over for those tests that iterate over the providers in an inner loop. Future work...
There was a problem hiding this comment.
One down-side to returning a unique_ptr when the caller wants a shared_ptr is that the object is no longer co-allocated with its control block. make_shared does one allocation for both the control block and the object.
gaaclarke
left a comment
There was a problem hiding this comment.
Thanks, changes are looking good. I just have 2 outstanding requests:
- Mitigate the IsValid design for the newly added class
- return unique_ptr instead of shared_ptr from the factory function
| if (pixels_.get()) { | ||
| pixmap_.reset(info, pixels_.get(), info.minRowBytes()); | ||
| if (raster_image->readPixels(pixmap_, 0, 0)) { | ||
| is_valid_ = true; |
There was a problem hiding this comment.
Yea, trying to fix it everywhere is out of scope of this PR. But since we are editing this class we can do the right thing here by introducing the factory method and always returning true for IsValid().
#185270 created a refactored SurfaceProvider API that enables better control over which backend tests and benchmarks can use. While the primitive rendering benchmarks were updated for the new code, the rendering validation tests (
display_list_rendertests) were still using a hybrid of the old API and some back doors to test on various platforms. This PR updates the rendering tests to use the new API, fills out its implementation on all of the supported platforms and makes the rendering validation tests more appropriate to run on all platforms.It also removes a few outdated tests that we no longer care about, such as:
We still test the following:
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on [Discord].
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.