New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle external image life time in api.delete_image() call #636
Handle external image life time in api.delete_image() call #636
Conversation
@@ -1451,7 +1472,17 @@ pub struct ExternalImage { | |||
/// Interface that an application can implement | |||
/// to support providing external image buffers. | |||
pub trait ExternalImageHandler { | |||
// Get the associate external image from the id. It also implies that the | |||
// image is held by WR. The WR client will keep the life time for this | |||
// image until the release() call. |
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: Please make these doc coments (///
instead of //
)
I'm not convinced this handling needs to be integrated into WR. Can't we move this responsibility to the user by doing the following scheme:
In this scheme, the only APIs that WR needs to provide are:
@glennw @JerryShih does it make sense? |
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.
Looks good, just a few minor issues.
} | ||
ResultMsg::NewFrame(frame, profile_counters) => { | ||
// When a new frame is ready, we could start to update all pending external | ||
// image request here. | ||
if !self.pending_external_image_updates.is_empty() { |
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 it's probably more "correct" to move this loop to the end of the render() call.
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.
@glennw
We could release the unused external image asap here. These external images will not be used because we set a new frame. If we move the deletion to the end of render() call, we just extend the life-time of these images.
What do you think for this reason?
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 what could happen is this sequence:
dl_builder.add_image(ext_image);
api.add_dl(dl_builder).
api.delete_image(blah)
In this case, it seems possible to me that you could have an image in the current frame's display list, which is also in the pending release list? That's why it seems safer to me to do it at the end of render(). The update() and render() are called directly after each other, so I don't think it will have any major impact on extra lifetimes. Does that seem possible to you? (I might be over-thinking 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.
api.add_image(A)
dl.push_image() with A
set_root_dl(frame 1)
update()
render() <= the frame 1 is not ready, draw frame 0 instead
api.delete_image(A)
api.add_image(B)
dl.push_image with B
update()
render() <= the frame 1 is ready now, draw with A
So, we the image A will still be used until frame 2 ready. That's why I put the release() call when we receives the new frame.
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.
dl_builder.add_image(ext_image);
api.add_dl(dl_builder).
api.delete_image(blah)
If we have this usage, it will have a panic in block_until_all_resources_added(). So, that problem is not related to this patch.
let handler = self.external_image_handler | ||
.as_mut() | ||
.expect("Found external image updates, but no handler set!"); | ||
let mut pending_external_image_updates = mem::replace(&mut self.pending_external_image_updates, Vec::new()); |
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 line shouldn't be necessary - the loop could just be self.pending_external_image_updates.drain(..)
I think.
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
let value = self.image_templates.remove(&image_key); | ||
|
||
// If the key is associate to an external image, pass the external id to render for cleanup. | ||
if let Some(image) = value { |
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.
Let's print a error if image is not Some(..) here as well, since that indicates a bug (such as double deletion of an image key).
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
If we call Render::update() and Render::render() multiple times without update the display_list, when should we release the resources? The user will receive several callback with the same frame id. |
@kvark I think it's probably fine to have this code in WR. Even though it's probably possible for the user to handle it externally, it seems like we could make it a safer interface by managing this internally. Thoughts? |
@glennw |
@kvark , could you please review my updates? Glenn is on PTO. |
Right, but this is easily workable. You could free the resources associated with frame N when you get a callback of frame N+1.
Will make sure to review today.
It's not obvious to me. The external image stuff is not allocated/owned by WR, so we should not be responsible for freeing it either. The less API surface is the better for me. How do you see this being a part of WR to be potentially safer? |
@@ -344,6 +344,22 @@ pub enum TextureUpdateOp { | |||
Grow(u32, u32, ImageFormat, TextureFilter, RenderTargetMode), | |||
} | |||
|
|||
pub struct ExternalImageUpdateList { |
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.
Are you planning to extend this struct? Otherwise, it could be turned into an alias (given that it only has one field and it's public, and no actual logic in the methods):
pub type ExternalImageUpdateList = Vec<ExternalImageId>;
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
@@ -1409,7 +1423,7 @@ impl Renderer { | |||
&projection); | |||
} | |||
|
|||
self.release_external_textures(); | |||
self.unlock_external_images(); |
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 you missed the corresponding lock()
call somewhere
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
Please squash it as well. |
The removing condition will become complicated. You should remember the frame id for each external image. Then, you also need to have a flag to check the external image is be called with api.delete_image(). We should not delete an external image that is still exist in resource cache.
I will update a squashed version later. |
9df394d
to
e2bfc7e
Compare
@@ -1263,7 +1265,7 @@ impl Renderer { | |||
let props = &deferred_resolve.image_properties; | |||
let external_id = props.external_id | |||
.expect("BUG: Deferred resolves must be external images!"); | |||
let image = handler.get(external_id); | |||
let image = handler.lock(external_id); |
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.
turn to use lock() here.
self.pending_texture_updates.push(texture_update_list); | ||
|
||
// When a new frame is ready, we could start to update all pending external image requests here. | ||
self.release_external_images(external_image_update_list); |
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.
Only release the external images used in previous frames when a new frame is ready.
@kvark , the updated patch is ready. |
Yes, that's what I meant originally:
I don't see which safety guarantees WR can impose on the external resources anyway, so I don't see a reason to complicate WR with an attempt to share any responsibility. The user should be fully responsible for the lifetime of those, and all information it needs to do so is just that frame ID. So, if the Renderer is done with frame N+1 (or later), the caller can free the resources associated with N. WR's delete_image() for the external resources would just clean up internal structures and not do anything dangerous by itself. I realize that my suggestion is a little hand-wavy, but I believe we should consider this as a way of minimal API to support the externally owned objects. Looking forward to hear what @glennw thinks about the possible safety extras we can get from a fatter API. |
@kvark I think you're quite right - this could be handled on the client side with a smaller API. I don't think having this API necessarily makes anything safer (that was bad wording on my part). But I do think if we can handle these details internally, it makes it easier for other clients to use the API correctly and safely (i.e. having the code to manage this in one place in WR is probably better than Servo, Gecko and other API clients having to correctly implement the semantics above). Thoughts? |
On the gecko side we're about to make some substantial changes how the different WebRender parts are organized and threads, so some of the assumptions that this PR has been designed against will probably change. While it's good to move forward with this and start iterating, I don't think we need to get all of details right just now. We can come back on how much is handled inside or outside of WebRender as the moving pieces come together. |
Given what @nical said, It looks like a classical textbook case for a fork, given that you need to play with API/internals and you don't know |
☔ The latest upstream changes (presumably #648) made this pull request unmergeable. Please resolve the merge conflicts. |
@kvark @glennw @nical |
I'm fine with API experiments happening here in upstream - so I'm happy to either merge this (once rebased and review is finished) or leave it until things settle down more - whatever will make it easiest for you. |
Now, the ExternalImageHandler has lock(), unlock() and release() callback functions. Use lock() instead of get() callback in WR render update_deferred_resolves().
Release the external images when a new frame is ready. Merge ResultMsg::UpdateTextureData and NewFrame::NewFrame message into one message.
e2bfc7e
to
ba33b43
Compare
@bors-servo r=glennw |
📌 Commit ba33b43 has been approved by |
⚡ Test exempted - status |
Handle external image life time in api.delete_image() call Currently, the delete_image() call just removes the image_key in the hash_map. If the key is associate to an external image, we should release that image using the ExternalImageHandler callbacks. Since the ExternalImageHandler callbacks are only used in renderer, a pending releasing array is used. Then, pass this array to the renderer for ExternalImageHandler::release() call. @nical @glennw <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/636) <!-- Reviewable:end -->
Currently, the delete_image() call just removes the image_key in the hash_map. If the key is associate to an external image, we should release that image using the ExternalImageHandler callbacks.
Since the ExternalImageHandler callbacks are only used in renderer, a pending releasing array is used. Then, pass this array to the renderer for ExternalImageHandler::release() call.
@nical @glennw
This change is