core: refactor and improve surface commit (#9805)

* make CHLBufferReference not a SP anymore

* copy over release and acquire points in CHLBufferReference

* use CHLBufferReference in screencopy and toplevel export

TODO: use CHLBufferReference in direct scanout properly
      the only problem is the scanout buffer release timing,
      specifically the onBackendRelease mechanism

* cleanup SSurfaceState and surface pending commit tracking

* move surface code from DRMSyncobj, and move acquire to SSurfaceState

* use queue for comitted pending surface states like proto says

"The content update is placed in a queue until it becomes active." - wl_surface::commit

* drop, not release, prev buffer if 2nd buffer wl_surface.attach is sent

"A wl_buffer that has been attached and then replaced by another attach instead of committed will not receive a release event, and is not used by the compositor." - wl_surface::attach
This commit is contained in:
Ikalco 2025-04-07 14:03:27 -05:00 committed by GitHub
parent 70ae99f521
commit da86db43d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 285 additions and 223 deletions

View file

@ -71,23 +71,32 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
resource->setOnDestroy([this](CWlSurface* r) { destroy(); });
resource->setAttach([this](CWlSurface* r, wl_resource* buffer, int32_t x, int32_t y) {
pending.offset = {x, y};
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_BUFFER | SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_OFFSET;
pending.updated.buffer = true;
pending.updated.offset = true;
if (!buffer) {
pending.buffer.reset();
pending.texture.reset();
pending.bufferSize = Vector2D{};
pending.offset = {x, y};
if (pending.buffer)
pending.buffer.drop();
auto buf = buffer ? CWLBufferResource::fromResource(buffer) : nullptr;
if (buf && buf->buffer) {
pending.buffer = CHLBufferReference(buf->buffer.lock());
pending.texture = buf->buffer->texture;
pending.size = buf->buffer->size;
pending.bufferSize = buf->buffer->size;
} else {
auto res = CWLBufferResource::fromResource(buffer);
pending.buffer = res && res->buffer ? makeShared<CHLBufferReference>(res->buffer.lock(), self.lock()) : nullptr;
pending.size = res && res->buffer ? res->buffer->size : Vector2D{};
pending.texture = res && res->buffer ? res->buffer->texture : nullptr;
pending.bufferSize = res && res->buffer ? res->buffer->size : Vector2D{};
pending.buffer = {};
pending.texture.reset();
pending.size = Vector2D{};
pending.bufferSize = Vector2D{};
}
if (pending.bufferSize != current.bufferSize)
pending.bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}};
if (pending.bufferSize != current.bufferSize) {
pending.updated.damage = true;
pending.bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}};
}
});
resource->setCommit([this](CWlSurface* r) {
@ -109,34 +118,78 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
events.precommit.emit();
if (pending.rejected) {
pending.rejected = false;
dropPendingBuffer();
return;
}
if (!syncobj)
commitPendingState(pending);
if ((!pending.updated.buffer) || // no new buffer attached
(!pending.buffer && !pending.texture) || // null buffer attached
(!pending.updated.acquire && pending.buffer->isSynchronous()) // synchronous buffers (ex. shm) can be read immediately
) {
commitState(pending);
pending.reset();
return;
}
// save state while we wait for buffer to become ready
const auto& state = pendingStates.emplace(makeUnique<SSurfaceState>(pending));
pending.reset();
auto whenReadable = [this, surf = self, state = WP<SSurfaceState>(pendingStates.back())] {
if (!surf || state.expired())
return;
while (!pendingStates.empty() && pendingStates.front() != state) {
commitState(*pendingStates.front());
pendingStates.pop();
}
commitState(*pendingStates.front());
pendingStates.pop();
};
if (state->updated.acquire) {
// wait on acquire point for this surface, from explicit sync protocol
state->acquire.addWaiter(whenReadable);
} else if (state->buffer->dmabuf().success) {
// https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#implicit-fence-poll-support
// TODO: wait for the dma-buf fd's to become readable
whenReadable();
} else {
// huh??? only buffers with acquire or dmabuf should get through here...
Debug::log(ERR, "BUG THIS: wl_surface.commit: non-acquire non-dmabuf buffers needs wait...");
whenReadable();
}
});
resource->setDamage([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_DAMAGE;
pending.updated.damage = true;
pending.damage.add(CBox{x, y, w, h});
});
resource->setDamageBuffer([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_DAMAGE;
pending.updated.damage = true;
pending.bufferDamage.add(CBox{x, y, w, h});
});
resource->setSetBufferScale([this](CWlSurface* r, int32_t scale) {
if (scale == pending.scale)
return;
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_SCALE | SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_DAMAGE;
pending.updated.scale = true;
pending.updated.damage = true;
pending.scale = scale;
pending.bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}};
});
resource->setSetBufferTransform([this](CWlSurface* r, uint32_t tr) {
if (tr == pending.transform)
return;
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_TRANSFORM | SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_DAMAGE;
pending.updated.transform = true;
pending.updated.damage = true;
pending.transform = (wl_output_transform)tr;
pending.bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}};
});
@ -147,7 +200,7 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
return;
}
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_INPUT;
pending.updated.input = true;
auto RG = CWLRegionResource::fromResource(region);
pending.input = RG->region;
@ -159,7 +212,7 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
return;
}
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_OPAQUE;
pending.updated.opaque = true;
auto RG = CWLRegionResource::fromResource(region);
pending.opaque = RG->region;
@ -168,8 +221,8 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
resource->setFrame([this](CWlSurface* r, uint32_t id) { callbacks.emplace_back(makeShared<CWLCallbackResource>(makeShared<CWlCallback>(pClient, 1, id))); });
resource->setOffset([this](CWlSurface* r, int32_t x, int32_t y) {
pending.updated |= SSurfaceState::eUpdatedProperties::SURFACE_UPDATED_OFFSET;
pending.offset = {x, y};
pending.updated.offset = true;
pending.offset = {x, y};
});
}
@ -188,11 +241,11 @@ void CWLSurfaceResource::destroy() {
}
void CWLSurfaceResource::dropPendingBuffer() {
pending.buffer.reset();
pending.buffer = {};
}
void CWLSurfaceResource::dropCurrentBuffer() {
current.buffer.reset();
current.buffer = {};
}
SP<CWLSurfaceResource> CWLSurfaceResource::fromResource(wl_resource* res) {
@ -279,7 +332,6 @@ void CWLSurfaceResource::resetRole() {
}
void CWLSurfaceResource::bfHelper(std::vector<SP<CWLSurfaceResource>> const& nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data) {
std::vector<SP<CWLSurfaceResource>> nodes2;
nodes2.reserve(nodes.size() * 2);
@ -424,13 +476,12 @@ CBox CWLSurfaceResource::extends() {
return full.getExtents();
}
void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
void CWLSurfaceResource::commitState(SSurfaceState& state) {
auto lastTexture = current.texture;
current.updateFrom(state);
state.updated = 0;
if (current.buffer) {
if (current.buffer->buffer->isSynchronous())
if (current.buffer->isSynchronous())
current.updateSynchronousTexture(lastTexture);
// if the surface is a cursor, update the shm buffer
@ -465,7 +516,7 @@ void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
// release the buffer if it's synchronous (SHM) as update() has done everything thats needed
// so we can let the app know we're done.
// if it doesn't have a role, we can't release it yet, in case it gets turned into a cursor.
if (current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous() && role->role() != SURFACE_ROLE_UNASSIGNED)
if (current.buffer && current.buffer->isSynchronous() && role->role() != SURFACE_ROLE_UNASSIGNED)
dropCurrentBuffer();
}
@ -473,7 +524,7 @@ void CWLSurfaceResource::updateCursorShm(CRegion damage) {
if (damage.empty())
return;
auto buf = current.buffer ? current.buffer->buffer : SP<IHLBuffer>{};
auto buf = current.buffer ? current.buffer : SP<IHLBuffer>{};
if UNLIKELY (!buf)
return;