summaryrefslogtreecommitdiff
path: root/chromium/ui/ozone/platform/wayland/gpu/wayland_canvas_surface.cc
blob: fe1040e526317aa479084c0ae0da9217646b0c5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/ozone/platform/wayland/gpu/wayland_canvas_surface.h"

#include <memory>
#include <utility>

#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/numerics/checked_math.h"
#include "base/posix/eintr_wrapper.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/vsync_provider.h"
#include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"

namespace ui {

namespace {

// The maximum number of buffers we allow to be created.
constexpr uint32_t kMaxNumbferOfBuffers = 3;

}  // namespace

size_t CalculateStride(int width) {
  base::CheckedNumeric<size_t> stride(width);
  stride *= 4;
  return stride.ValueOrDie();
}

class WaylandCanvasSurface::SharedMemoryBuffer {
 public:
  SharedMemoryBuffer(uint32_t buffer_id,
                     gfx::AcceleratedWidget widget,
                     WaylandBufferManagerGpu* buffer_manager)
      : buffer_id_(buffer_id),
        widget_(widget),
        buffer_manager_(buffer_manager) {
    DCHECK(buffer_manager_);
  }
  ~SharedMemoryBuffer() { buffer_manager_->DestroyBuffer(widget_, buffer_id_); }

  // Returns SkSurface, which the client can use to write to this buffer.
  sk_sp<SkSurface> sk_surface() const { return sk_surface_; }

  // Returns the id of the buffer.
  uint32_t buffer_id() const { return buffer_id_; }

  // Tells if the buffer is pending to be processed. Set on
  // SetPendingDamageRegion calls.
  bool pending() const { return pending_; }

  // Initializes the shared memory and asks Wayland to import a shared memory
  // based wl_buffer, which can be attached to a surface and have its contents
  // shown on a screen.
  bool Initialize(const gfx::Size& size) {
    base::CheckedNumeric<size_t> checked_length(size.width());
    checked_length *= size.height();
    checked_length *= 4;
    if (!checked_length.IsValid())
      return false;

    base::UnsafeSharedMemoryRegion shm_region =
        base::UnsafeSharedMemoryRegion::Create(checked_length.ValueOrDie());
    if (!shm_region.IsValid())
      return false;

    shm_mapping_ = shm_region.Map();
    if (!shm_mapping_.IsValid())
      return false;

    base::subtle::PlatformSharedMemoryRegion platform_shm =
        base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
            std::move(shm_region));
    base::subtle::ScopedFDPair fd_pair = platform_shm.PassPlatformHandle();
    buffer_manager_->CreateShmBasedBuffer(
        std::move(fd_pair.fd), checked_length.ValueOrDie(), size, buffer_id_);

    sk_surface_ = SkSurface::MakeRasterDirect(
        SkImageInfo::MakeN32Premul(size.width(), size.height()),
        shm_mapping_.memory(), CalculateStride(size.width()));
    if (!sk_surface_)
      return false;

    dirty_region_.setRect(gfx::RectToSkIRect(gfx::Rect(size)));
    return true;
  }

  void CommitBuffer(const gfx::Rect& damage) {
    buffer_manager_->CommitBuffer(widget_, buffer_id_, damage);
  }

  void OnSubmissionCompleted() {
    DCHECK(pending_);
    pending_ = false;
  }

  void UpdateDirtyRegion(const gfx::Rect& damage, SkRegion::Op op) {
    SkIRect sk_damage = gfx::RectToSkIRect(damage);
    dirty_region_.op(sk_damage, op);
  }

  void CopyDirtyRegionFrom(SharedMemoryBuffer* buffer) {
    DCHECK_NE(this, buffer);
    const size_t stride = CalculateStride(sk_surface_->width());
    for (SkRegion::Iterator i(dirty_region_); !i.done(); i.next()) {
      uint8_t* dst_ptr =
          static_cast<uint8_t*>(shm_mapping_.memory()) +
          i.rect().x() * SkColorTypeBytesPerPixel(kN32_SkColorType) +
          i.rect().y() * stride;
      buffer->sk_surface_->readPixels(
          SkImageInfo::MakeN32Premul(i.rect().width(), i.rect().height()),
          dst_ptr, stride, i.rect().x(), i.rect().y());
    }
    dirty_region_.setEmpty();
  }

  void SetPendingDamageRegion(const gfx::Rect& damage) {
    DCHECK(!pending_);
    pending_damage_region_ = damage;
    pending_ = true;
  }

  gfx::Rect pending_damage_region() {
    return std::move(pending_damage_region_);
  }

 private:
  // The id of the buffer this surface is backed.
  const uint32_t buffer_id_;

  // Says if the buffer is pending to be submitted.
  bool pending_ = false;

  // The widget this buffer is created for.
  const gfx::AcceleratedWidget widget_;

  // Non-owned pointer to the buffer manager on the gpu process/thread side.
  WaylandBufferManagerGpu* const buffer_manager_;

  // Shared memory for the buffer.
  base::WritableSharedMemoryMapping shm_mapping_;

  // The SkSurface the clients can draw to show the surface on screen.
  sk_sp<SkSurface> sk_surface_;

  // The region of the buffer that's not up-to-date.
  SkRegion dirty_region_;

  // Pending damage region if the buffer is pending to be submitted.
  gfx::Rect pending_damage_region_;

  DISALLOW_COPY_AND_ASSIGN(SharedMemoryBuffer);
};

WaylandCanvasSurface::WaylandCanvasSurface(
    WaylandBufferManagerGpu* buffer_manager,
    gfx::AcceleratedWidget widget)
    : buffer_manager_(buffer_manager), widget_(widget) {
  buffer_manager_->RegisterSurface(widget_, this);
}

WaylandCanvasSurface::~WaylandCanvasSurface() {
  buffer_manager_->UnregisterSurface(widget_);
}

sk_sp<SkSurface> WaylandCanvasSurface::GetSurface() {
  DCHECK(!pending_buffer_)
      << "The previous pending buffer has not been presented yet";

  for (const auto& buffer : buffers_) {
    if (!buffer->pending()) {
      pending_buffer_ = buffer.get();
      break;
    }
  }

  if (!pending_buffer_) {
    if (buffers_.size() >= kMaxNumbferOfBuffers) {
      // We have achieved the maximum number of buffers we can create. Wait for
      // a free buffer.
      return nullptr;
    }
    auto buffer = CreateSharedMemoryBuffer();
    if (!buffer)
      return nullptr;

    pending_buffer_ = buffer.get();
    buffers_.push_back(std::move(buffer));
  }

  DCHECK(pending_buffer_ && !pending_buffer_->pending());
  return pending_buffer_->sk_surface();
}

void WaylandCanvasSurface::ResizeCanvas(const gfx::Size& viewport_size) {
  if (size_ == viewport_size)
    return;
  // TODO(https://crbug.com/930667): We could implement more efficient resizes
  // by allocating buffers rounded up to larger sizes, and then reusing them if
  // the new size still fits (but still reallocate if the new size is much
  // smaller than the old size).
  if (!buffers_.empty()) {
    buffers_.clear();
    current_buffer_ = nullptr;
    previous_buffer_ = nullptr;
    pending_buffer_ = nullptr;
    unsubmitted_buffers_.clear();
  }
  size_ = viewport_size;
}

void WaylandCanvasSurface::PresentCanvas(const gfx::Rect& damage) {
  if (!pending_buffer_)
    return;

  pending_buffer_->SetPendingDamageRegion(damage);
  unsubmitted_buffers_.push_back(pending_buffer_);
  pending_buffer_ = nullptr;

  ProcessUnsubmittedBuffers();
}

std::unique_ptr<gfx::VSyncProvider>
WaylandCanvasSurface::CreateVSyncProvider() {
  // TODO(https://crbug.com/930662): This can be implemented with information
  // from presentation feedback.
  NOTIMPLEMENTED_LOG_ONCE();
  return nullptr;
}

void WaylandCanvasSurface::ProcessUnsubmittedBuffers() {
  DCHECK(!unsubmitted_buffers_.empty());

  if (!current_buffer_ && unsubmitted_buffers_.front()->pending()) {
    current_buffer_ = std::move(unsubmitted_buffers_.front());
    unsubmitted_buffers_.erase(unsubmitted_buffers_.begin());

    gfx::Rect damage = current_buffer_->pending_damage_region();

    // The buffer has been updated. Thus, the |damage| can be subtracted
    // from its dirty region.
    current_buffer_->UpdateDirtyRegion(damage, SkRegion::kDifference_Op);

    // Make sure the buffer is up-to-date by copying the outdated region from
    // the previous buffer.
    if (previous_buffer_ && previous_buffer_ != current_buffer_)
      current_buffer_->CopyDirtyRegionFrom(previous_buffer_);

    // As long as the |current_buffer_| has been updated, add dirty region to
    // other buffers to make sure their regions will be updated with up-to-date
    // content.
    for (auto& buffer : buffers_) {
      if (buffer.get() != current_buffer_)
        buffer->UpdateDirtyRegion(damage, SkRegion::kUnion_Op);
    }

    current_buffer_->CommitBuffer(damage);
  }
}

void WaylandCanvasSurface::OnSubmission(uint32_t buffer_id,
                                        const gfx::SwapResult& swap_result) {
  // Upper layer does not care about the submission result, and the buffer may
  // be destroyed by this time (when the surface is resized, for example).
  if (!current_buffer_)
    return;

  DCHECK_EQ(current_buffer_->buffer_id(), buffer_id);

  auto* buffer = current_buffer_;
  previous_buffer_ = buffer;
  current_buffer_ = nullptr;

  buffer->OnSubmissionCompleted();

  if (!unsubmitted_buffers_.empty())
    ProcessUnsubmittedBuffers();
}

void WaylandCanvasSurface::OnPresentation(
    uint32_t buffer_id,
    const gfx::PresentationFeedback& feedback) {
  // TODO(https://crbug.com/930662): this can be used for the vsync provider.
  NOTIMPLEMENTED_LOG_ONCE();
}

std::unique_ptr<WaylandCanvasSurface::SharedMemoryBuffer>
WaylandCanvasSurface::CreateSharedMemoryBuffer() {
  DCHECK(!size_.IsEmpty());

  auto canvas_buffer = std::make_unique<SharedMemoryBuffer>(
      ++buffer_id_, widget_, buffer_manager_);
  return canvas_buffer->Initialize(size_) ? std::move(canvas_buffer) : nullptr;
}

}  // namespace ui