summaryrefslogtreecommitdiffstats
path: root/chromium/media/gpu/ipc/service/picture_buffer_manager.cc
blob: 9cd5b1e16644706aae168ef33ade843c12ecd5a9 (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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// Copyright 2018 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 "media/gpu/ipc/service/picture_buffer_manager.h"

#include <map>
#include <set>
#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "media/base/video_util.h"

namespace media {

namespace {

// Generates nonnegative picture buffer IDs, which are assumed to be unique.
int32_t NextID(int32_t* counter) {
  int32_t value = *counter;
  *counter = (*counter + 1) & 0x3FFFFFFF;
  return value;
}

class PictureBufferManagerImpl : public PictureBufferManager {
 public:
  explicit PictureBufferManagerImpl(
      ReusePictureBufferCB reuse_picture_buffer_cb)
      : reuse_picture_buffer_cb_(std::move(reuse_picture_buffer_cb)) {
    DVLOG(1) << __func__;
  }

  void Initialize(
      scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
      scoped_refptr<CommandBufferHelper> command_buffer_helper) override {
    DVLOG(1) << __func__;
    DCHECK(!gpu_task_runner_);

    gpu_task_runner_ = std::move(gpu_task_runner);
    command_buffer_helper_ = std::move(command_buffer_helper);
  }

  bool CanReadWithoutStalling() override {
    DVLOG(3) << __func__;

    base::AutoLock lock(picture_buffers_lock_);

    // If at least one picture buffer is not in use, predict that the VDA can
    // use it to output another picture.
    bool has_assigned_picture_buffer = false;
    for (const auto& it : picture_buffers_) {
      if (!it.second.dismissed) {
        // Note: If a picture buffer is waiting for SyncToken release, that
        // release is already in some command buffer (or the wait is invalid).
        // The wait will complete without further interaction from the client.
        if (it.second.output_count == 0)
          return true;
        has_assigned_picture_buffer = true;
      }
    }

    // If there are no assigned picture buffers, predict that the VDA will
    // request some.
    return !has_assigned_picture_buffer;
  }

  std::vector<PictureBuffer> CreatePictureBuffers(
      uint32_t count,
      VideoPixelFormat pixel_format,
      uint32_t planes,
      gfx::Size texture_size,
      uint32_t texture_target) override {
    DVLOG(2) << __func__;
    DCHECK(gpu_task_runner_);
    DCHECK(gpu_task_runner_->BelongsToCurrentThread());
    DCHECK(count);
    DCHECK(planes);
    DCHECK_LE(planes, static_cast<uint32_t>(VideoFrame::kMaxPlanes));

    // TODO(sandersd): Consider requiring that CreatePictureBuffers() is called
    // with the context current.
    if (!command_buffer_helper_->MakeContextCurrent()) {
      DVLOG(1) << "Failed to make context current";
      return std::vector<PictureBuffer>();
    }

    std::vector<PictureBuffer> picture_buffers;
    for (uint32_t i = 0; i < count; i++) {
      PictureBufferData picture_data = {pixel_format, texture_size};

      for (uint32_t j = 0; j < planes; j++) {
        // Create a texture for this plane.
        GLuint service_id = command_buffer_helper_->CreateTexture(
            texture_target, GL_RGBA, texture_size.width(),
            texture_size.height(), GL_RGBA, GL_UNSIGNED_BYTE);
        DCHECK(service_id);
        picture_data.service_ids.push_back(service_id);

        // The texture is not cleared yet, but it will be before the VDA outputs
        // it. Rather than requiring output to happen on the GPU thread, mark
        // the texture as cleared immediately.
        command_buffer_helper_->SetCleared(service_id);

        // Generate a mailbox while we are still on the GPU thread.
        picture_data.mailbox_holders[j] = gpu::MailboxHolder(
            command_buffer_helper_->CreateMailbox(service_id), gpu::SyncToken(),
            texture_target);
      }

      // Generate a picture buffer ID and record the picture buffer.
      int32_t picture_buffer_id = NextID(&picture_buffer_id_);
      {
        base::AutoLock lock(picture_buffers_lock_);
        DCHECK(!picture_buffers_.count(picture_buffer_id));
        picture_buffers_[picture_buffer_id] = picture_data;
      }

      // Since our textures have no client IDs, we reuse the service IDs as
      // convenient unique identifiers.
      //
      // TODO(sandersd): Refactor the bind image callback to use service IDs so
      // that we can get rid of the client IDs altogether.
      picture_buffers.emplace_back(
          picture_buffer_id, texture_size, picture_data.service_ids,
          picture_data.service_ids, texture_target, pixel_format);
    }
    return picture_buffers;
  }

  bool DismissPictureBuffer(int32_t picture_buffer_id) override {
    DVLOG(2) << __func__ << "(" << picture_buffer_id << ")";

    base::AutoLock lock(picture_buffers_lock_);

    const auto& it = picture_buffers_.find(picture_buffer_id);
    if (it == picture_buffers_.end() || it->second.dismissed) {
      DVLOG(1) << "Unknown picture buffer " << picture_buffer_id;
      return false;
    }

    it->second.dismissed = true;

    // If the picture buffer is not in use, it should be destroyed immediately.
    if (!it->second.IsInUse()) {
      gpu_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&PictureBufferManagerImpl::DestroyPictureBuffer, this,
                         picture_buffer_id));
    }

    return true;
  }

  void DismissAllPictureBuffers() override {
    DVLOG(2) << __func__;

    std::vector<int32_t> assigned_picture_buffer_ids;
    {
      base::AutoLock lock(picture_buffers_lock_);

      for (const auto& it : picture_buffers_) {
        if (!it.second.dismissed)
          assigned_picture_buffer_ids.push_back(it.first);
      }
    }

    for (int32_t picture_buffer_id : assigned_picture_buffer_ids)
      DismissPictureBuffer(picture_buffer_id);
  }

  scoped_refptr<VideoFrame> CreateVideoFrame(Picture picture,
                                             base::TimeDelta timestamp,
                                             gfx::Rect visible_rect,
                                             gfx::Size natural_size) override {
    DVLOG(2) << __func__ << "(" << picture.picture_buffer_id() << ")";
    DCHECK(!picture.size_changed());
    DCHECK(!picture.texture_owner());
    DCHECK(!picture.wants_promotion_hint());

    base::AutoLock lock(picture_buffers_lock_);

    int32_t picture_buffer_id = picture.picture_buffer_id();

    // Verify that the picture buffer is available.
    const auto& it = picture_buffers_.find(picture_buffer_id);
    if (it == picture_buffers_.end() || it->second.dismissed) {
      DVLOG(1) << "Unknown picture buffer " << picture_buffer_id;
      return nullptr;
    }

    // Ensure that the picture buffer is large enough.
    PictureBufferData& picture_buffer_data = it->second;
    if (!gfx::Rect(picture_buffer_data.texture_size).Contains(visible_rect)) {
      DLOG(WARNING) << "visible_rect " << visible_rect.ToString()
                    << " exceeds coded_size "
                    << picture_buffer_data.texture_size.ToString();
      double pixel_aspect_ratio =
          GetPixelAspectRatio(visible_rect, natural_size);
      visible_rect.Intersect(gfx::Rect(picture_buffer_data.texture_size));
      natural_size = GetNaturalSize(visible_rect, pixel_aspect_ratio);
    }

    // Record the output.
    picture_buffer_data.output_count++;

    // Create and return a VideoFrame for the picture buffer.
    scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
        picture_buffer_data.pixel_format, picture_buffer_data.mailbox_holders,
        base::BindRepeating(&PictureBufferManagerImpl::OnVideoFrameDestroyed,
                            this, picture_buffer_id),
        picture_buffer_data.texture_size, visible_rect, natural_size,
        timestamp);

    frame->set_color_space(picture.color_space());

    if (picture.allow_overlay())
      frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true);

    // TODO(sandersd): Provide an API for VDAs to control this.
    frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, true);

    return frame;
  }

 private:
  ~PictureBufferManagerImpl() override {
    DVLOG(1) << __func__;
    DCHECK(picture_buffers_.empty() || !command_buffer_helper_->HasStub());
  }

  void OnVideoFrameDestroyed(int32_t picture_buffer_id,
                             const gpu::SyncToken& sync_token) {
    DVLOG(3) << __func__ << "(" << picture_buffer_id << ")";

    base::AutoLock lock(picture_buffers_lock_);

    // Record the picture buffer as waiting for SyncToken release (even if it
    // has been dismissed already).
    const auto& it = picture_buffers_.find(picture_buffer_id);
    DCHECK(it != picture_buffers_.end());
    DCHECK_GT(it->second.output_count, 0);
    it->second.output_count--;
    it->second.waiting_for_synctoken_count++;

    // Wait for the SyncToken release.
    gpu_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(
            &CommandBufferHelper::WaitForSyncToken, command_buffer_helper_,
            sync_token,
            base::BindOnce(&PictureBufferManagerImpl::OnSyncTokenReleased, this,
                           picture_buffer_id)));
  }

  void OnSyncTokenReleased(int32_t picture_buffer_id) {
    DVLOG(3) << __func__ << "(" << picture_buffer_id << ")";
    DCHECK(gpu_task_runner_);
    DCHECK(gpu_task_runner_->BelongsToCurrentThread());

    // Remove the pending wait.
    bool is_assigned = false;
    bool is_in_use = true;
    {
      base::AutoLock lock(picture_buffers_lock_);
      const auto& it = picture_buffers_.find(picture_buffer_id);
      DCHECK(it != picture_buffers_.end());

      DCHECK_GT(it->second.waiting_for_synctoken_count, 0);
      it->second.waiting_for_synctoken_count--;

      is_assigned = !it->second.dismissed;
      is_in_use = it->second.IsInUse();
    }

    // If the picture buffer is still assigned, it is ready to be reused.
    // Otherwise it should be destroyed if it is no longer in use. Neither of
    // these operations should be done while holding the lock.
    if (is_assigned) {
      // The callback is called even if the picture buffer is still in use; the
      // client is expected to wait for all copies of a picture buffer to be
      // returned before reusing any textures.
      reuse_picture_buffer_cb_.Run(picture_buffer_id);
    } else if (!is_in_use) {
      DestroyPictureBuffer(picture_buffer_id);
    }
  }

  void DestroyPictureBuffer(int32_t picture_buffer_id) {
    DVLOG(3) << __func__ << "(" << picture_buffer_id << ")";
    DCHECK(gpu_task_runner_);
    DCHECK(gpu_task_runner_->BelongsToCurrentThread());

    std::vector<GLuint> service_ids;
    {
      base::AutoLock lock(picture_buffers_lock_);
      const auto& it = picture_buffers_.find(picture_buffer_id);
      DCHECK(it != picture_buffers_.end());
      DCHECK(it->second.dismissed);
      DCHECK(!it->second.IsInUse());
      service_ids = std::move(it->second.service_ids);
      picture_buffers_.erase(it);
    }

    if (!command_buffer_helper_->MakeContextCurrent())
      return;

    for (GLuint service_id : service_ids)
      command_buffer_helper_->DestroyTexture(service_id);
  }

  ReusePictureBufferCB reuse_picture_buffer_cb_;

  scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
  scoped_refptr<CommandBufferHelper> command_buffer_helper_;

  int32_t picture_buffer_id_ = 0;

  struct PictureBufferData {
    VideoPixelFormat pixel_format;
    gfx::Size texture_size;
    std::vector<GLuint> service_ids;
    gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
    bool dismissed = false;

    // The same picture buffer can be output from the VDA multiple times
    // concurrently, so the state is tracked using counts.
    //   |output_count|: Number of VideoFrames this picture buffer is bound to.
    //   |waiting_for_synctoken_count|: Number of returned frames that are
    //       waiting for SyncToken release.
    int output_count = 0;
    int waiting_for_synctoken_count = 0;

    bool IsInUse() const {
      return output_count > 0 || waiting_for_synctoken_count > 0;
    }
  };

  base::Lock picture_buffers_lock_;
  std::map<int32_t, PictureBufferData> picture_buffers_
      GUARDED_BY(picture_buffers_lock_);

  DISALLOW_COPY_AND_ASSIGN(PictureBufferManagerImpl);
};

}  // namespace

// static
scoped_refptr<PictureBufferManager> PictureBufferManager::Create(
    ReusePictureBufferCB reuse_picture_buffer_cb) {
  return base::MakeRefCounted<PictureBufferManagerImpl>(
      std::move(reuse_picture_buffer_cb));
}

}  // namespace media