DiscardableHandle Implementation
This change adds a DiscardableHandle class, which acts as the primary
synchronization mechanism for the GPU Discardable Memory system.
This is the first in a sequence of CLs which implement the GPU
Discardable Memory system. This class is currently only created/used by
unit test code.
BUG=706456
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Review-Url: https://codereview.chromium.org/2732223004
Cr-Commit-Position: refs/heads/master@{#460610}
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index 82c9e9a..6bad429 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -234,6 +234,7 @@
"command_buffer/common/command_buffer_mock.h",
"command_buffer/common/command_buffer_shared_test.cc",
"command_buffer/common/debug_marker_manager_unittest.cc",
+ "command_buffer/common/discardable_handle_unittest.cc",
"command_buffer/common/gles2_cmd_format_test.cc",
"command_buffer/common/gles2_cmd_format_test_autogen.h",
"command_buffer/common/gles2_cmd_utils_unittest.cc",
diff --git a/gpu/command_buffer/common/BUILD.gn b/gpu/command_buffer/common/BUILD.gn
index 3f9e147..4b69e04e 100644
--- a/gpu/command_buffer/common/BUILD.gn
+++ b/gpu/command_buffer/common/BUILD.gn
@@ -37,6 +37,8 @@
"constants.h",
"debug_marker_manager.cc",
"debug_marker_manager.h",
+ "discardable_handle.cc",
+ "discardable_handle.h",
"gles2_cmd_format.cc",
"gles2_cmd_format.h",
"gles2_cmd_format_autogen.h",
diff --git a/gpu/command_buffer/common/discardable_handle.cc b/gpu/command_buffer/common/discardable_handle.cc
new file mode 100644
index 0000000..987ad82
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2017 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 "gpu/command_buffer/common/discardable_handle.h"
+
+#include "base/atomicops.h"
+#include "gpu/command_buffer/common/buffer.h"
+
+namespace gpu {
+namespace {
+const int32_t kHandleDeleted = 0;
+const int32_t kHandleUnlocked = 1;
+const int32_t kHandleLockedStart = 2;
+
+} // namespace
+
+DiscardableHandleBase::DiscardableHandleBase(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id)
+ : buffer_(std::move(buffer)), byte_offset_(byte_offset), shm_id_(shm_id) {}
+
+DiscardableHandleBase::DiscardableHandleBase(
+ const DiscardableHandleBase& other) = default;
+DiscardableHandleBase::DiscardableHandleBase(DiscardableHandleBase&& other) =
+ default;
+DiscardableHandleBase::~DiscardableHandleBase() = default;
+DiscardableHandleBase& DiscardableHandleBase::operator=(
+ const DiscardableHandleBase& other) = default;
+DiscardableHandleBase& DiscardableHandleBase::operator=(
+ DiscardableHandleBase&& other) = default;
+
+bool DiscardableHandleBase::IsLockedForTesting() {
+ return kHandleLockedStart <= base::subtle::NoBarrier_Load(AsAtomic());
+}
+
+bool DiscardableHandleBase::IsDeletedForTesting() {
+ return kHandleDeleted == base::subtle::NoBarrier_Load(AsAtomic());
+}
+
+volatile base::subtle::Atomic32* DiscardableHandleBase::AsAtomic() const {
+ return reinterpret_cast<volatile base::subtle::Atomic32*>(
+ buffer_->GetDataAddress(byte_offset_, sizeof(base::subtle::Atomic32)));
+}
+
+ClientDiscardableHandle::ClientDiscardableHandle(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id)
+ : DiscardableHandleBase(std::move(buffer), byte_offset, shm_id) {
+ // Handle always starts locked.
+ base::subtle::NoBarrier_Store(AsAtomic(), kHandleLockedStart);
+}
+
+ClientDiscardableHandle::ClientDiscardableHandle(
+ const ClientDiscardableHandle& other) = default;
+ClientDiscardableHandle::ClientDiscardableHandle(
+ ClientDiscardableHandle&& other) = default;
+ClientDiscardableHandle& ClientDiscardableHandle::operator=(
+ const ClientDiscardableHandle& other) = default;
+ClientDiscardableHandle& ClientDiscardableHandle::operator=(
+ ClientDiscardableHandle&& other) = default;
+
+bool ClientDiscardableHandle::Lock() {
+ while (true) {
+ base::subtle::Atomic32 current_value =
+ base::subtle::NoBarrier_Load(AsAtomic());
+ if (current_value == kHandleDeleted) {
+ // Once a handle is deleted, it cannot be modified further.
+ return false;
+ }
+ base::subtle::Atomic32 new_value = current_value + 1;
+ // No barrier is needed, as any commands which depend on this operation
+ // will flow over the command buffer, which ensures a memory barrier
+ // between here and where these commands are executed on the GPU process.
+ base::subtle::Atomic32 previous_value =
+ base::subtle::NoBarrier_CompareAndSwap(AsAtomic(), current_value,
+ new_value);
+ if (current_value == previous_value) {
+ return true;
+ }
+ }
+}
+
+bool ClientDiscardableHandle::CanBeReUsed() const {
+ return kHandleDeleted == base::subtle::Acquire_Load(AsAtomic());
+}
+
+ServiceDiscardableHandle::ServiceDiscardableHandle(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id)
+ : DiscardableHandleBase(std::move(buffer), byte_offset, shm_id) {}
+
+ServiceDiscardableHandle::ServiceDiscardableHandle(
+ const ServiceDiscardableHandle& other) = default;
+ServiceDiscardableHandle::ServiceDiscardableHandle(
+ ServiceDiscardableHandle&& other) = default;
+ServiceDiscardableHandle& ServiceDiscardableHandle::operator=(
+ const ServiceDiscardableHandle& other) = default;
+ServiceDiscardableHandle& ServiceDiscardableHandle::operator=(
+ ServiceDiscardableHandle&& other) = default;
+
+void ServiceDiscardableHandle::Unlock() {
+ // No barrier is needed as all GPU process access happens on a single thread,
+ // and communication of dependent data between the GPU process and the
+ // renderer process happens across the command buffer and includes barriers.
+ base::subtle::NoBarrier_AtomicIncrement(AsAtomic(), -1);
+}
+
+bool ServiceDiscardableHandle::Delete() {
+ // No barrier is needed as all GPU process access happens on a single thread,
+ // and communication of dependent data between the GPU process and the
+ // renderer process happens across the command buffer and includes barriers.
+ return kHandleUnlocked == base::subtle::NoBarrier_CompareAndSwap(
+ AsAtomic(), kHandleUnlocked, kHandleDeleted);
+}
+
+} // namespace gpu
diff --git a/gpu/command_buffer/common/discardable_handle.h b/gpu/command_buffer/common/discardable_handle.h
new file mode 100644
index 0000000..a3b1769
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2017 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.
+
+#ifndef GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
+#define GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
+
+#include "base/memory/ref_counted.h"
+#include "gpu/gpu_export.h"
+
+namespace gpu {
+
+class Buffer;
+
+// DiscardableHandleBase is the base class for the discardable handle
+// implementation. In order to facilitate transfering handles across the
+// command buffer, DiscardableHandleBase is backed by a gpu::Buffer and an
+// offset into that buffer. It uses a single uint32_t of data at the given
+// offset.
+//
+// DiscardableHandleBase is never used directly, but is instead modified by the
+// Client/ServiceDiscardableHandle subclasses. These subclasses implement the
+// Lock/Unlock/Delete functionality, making it explicit which operations occur
+// in which process.
+//
+// Via these subclasses, a discardable handle can be transitioned between one
+// of three states:
+// ╔════════════╗ ╔════════════╗ ╔═══════════╗
+// ║ Locked ║ ──────> ║ Unlocked ║ ──────> ║ Deleted ║
+// ╚════════════╝ ╚════════════╝ ╚═══════════╝
+// └───────────<──────────┘
+//
+// Note that a handle can be locked multiple times, and stores a lock-count.
+class GPU_EXPORT DiscardableHandleBase {
+ public:
+ int32_t shm_id() const { return shm_id_; }
+ uint32_t byte_offset() const { return byte_offset_; }
+
+ // Test only functions.
+ bool IsLockedForTesting();
+ bool IsDeletedForTesting();
+
+ protected:
+ DiscardableHandleBase(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id);
+ DiscardableHandleBase(const DiscardableHandleBase& other);
+ DiscardableHandleBase(DiscardableHandleBase&& other);
+ DiscardableHandleBase& operator=(const DiscardableHandleBase& other);
+ DiscardableHandleBase& operator=(DiscardableHandleBase&& other);
+ ~DiscardableHandleBase();
+
+ volatile base::subtle::Atomic32* AsAtomic() const;
+
+ private:
+ scoped_refptr<Buffer> buffer_;
+ uint32_t byte_offset_ = 0;
+ uint32_t shm_id_ = 0;
+};
+
+// ClientDiscardableHandle enables the instantiation of a new discardable
+// handle (via the constructor), and can Lock an existing handle.
+class GPU_EXPORT ClientDiscardableHandle : public DiscardableHandleBase {
+ public:
+ ClientDiscardableHandle(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id);
+ ClientDiscardableHandle(const ClientDiscardableHandle& other);
+ ClientDiscardableHandle(ClientDiscardableHandle&& other);
+ ClientDiscardableHandle& operator=(const ClientDiscardableHandle& other);
+ ClientDiscardableHandle& operator=(ClientDiscardableHandle&& other);
+
+ // Tries to lock the handle. Returns true if successfully locked. Returns
+ // false if the handle has already been deleted on the service.
+ bool Lock();
+
+ // Returns true if the handle has been deleted on service side and can be
+ // re-used on the client.
+ bool CanBeReUsed() const;
+};
+
+// ServiceDiscardableHandle can wrap an existing handle (via the constructor),
+// and can unlock and delete this handle.
+class GPU_EXPORT ServiceDiscardableHandle : public DiscardableHandleBase {
+ public:
+ ServiceDiscardableHandle(scoped_refptr<Buffer> buffer,
+ uint32_t byte_offset,
+ int32_t shm_id);
+ ServiceDiscardableHandle(const ServiceDiscardableHandle& other);
+ ServiceDiscardableHandle(ServiceDiscardableHandle&& other);
+ ServiceDiscardableHandle& operator=(const ServiceDiscardableHandle& other);
+ ServiceDiscardableHandle& operator=(ServiceDiscardableHandle&& other);
+
+ // Unlocks the handle. This should always be paired with a client-side call
+ // to lock, or with a new handle, which starts locked.
+ void Unlock();
+
+ // Tries to delete the handle. Returns true if successfully deleted. Returns
+ // false if the handle is locked client-side and cannot be deleted.
+ bool Delete();
+};
+
+} // namespace gpu
+
+#endif // GPU_COMMAND_BUFFER_COMMON_DISCARDABLE_HANDLE_H_
diff --git a/gpu/command_buffer/common/discardable_handle_unittest.cc b/gpu/command_buffer/common/discardable_handle_unittest.cc
new file mode 100644
index 0000000..8bb01ae
--- /dev/null
+++ b/gpu/command_buffer/common/discardable_handle_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2017 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 "gpu/command_buffer/common/discardable_handle.h"
+#include "gpu/command_buffer/common/buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gpu {
+namespace {
+
+scoped_refptr<Buffer> MakeBufferForTesting(size_t num_handles) {
+ size_t size = sizeof(base::subtle::Atomic32) * num_handles;
+ std::unique_ptr<base::SharedMemory> shared_mem(new base::SharedMemory);
+ shared_mem->CreateAndMapAnonymous(size);
+ return MakeBufferFromSharedMemory(std::move(shared_mem), size);
+}
+
+} // namespace
+
+TEST(DiscardableHandleTest, BasicUsage) {
+ scoped_refptr<Buffer> buffer = MakeBufferForTesting(1);
+
+ uint32_t byte_offset = 0;
+ int32_t shm_id = 1;
+ ClientDiscardableHandle client_handle(buffer, byte_offset, shm_id);
+ EXPECT_EQ(client_handle.shm_id(), shm_id);
+ EXPECT_TRUE(client_handle.IsLockedForTesting());
+
+ ServiceDiscardableHandle service_handle(buffer, byte_offset, shm_id);
+ EXPECT_EQ(service_handle.shm_id(), shm_id);
+ EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+ EXPECT_FALSE(service_handle.Delete());
+ EXPECT_FALSE(service_handle.IsDeletedForTesting());
+ EXPECT_FALSE(client_handle.CanBeReUsed());
+
+ service_handle.Unlock();
+ EXPECT_FALSE(service_handle.IsLockedForTesting());
+ EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+ EXPECT_TRUE(client_handle.Lock());
+ EXPECT_TRUE(client_handle.IsLockedForTesting());
+ EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+ service_handle.Unlock();
+ EXPECT_FALSE(service_handle.IsLockedForTesting());
+ EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+ EXPECT_TRUE(service_handle.Delete());
+ EXPECT_TRUE(service_handle.IsDeletedForTesting());
+ EXPECT_TRUE(client_handle.CanBeReUsed());
+ EXPECT_FALSE(service_handle.IsLockedForTesting());
+ EXPECT_FALSE(client_handle.IsLockedForTesting());
+
+ EXPECT_FALSE(client_handle.Lock());
+ EXPECT_FALSE(service_handle.IsLockedForTesting());
+ EXPECT_FALSE(client_handle.IsLockedForTesting());
+ EXPECT_TRUE(service_handle.IsDeletedForTesting());
+ EXPECT_TRUE(client_handle.IsDeletedForTesting());
+}
+
+TEST(DiscardableHandleTest, MultiLock) {
+ scoped_refptr<Buffer> buffer = MakeBufferForTesting(1);
+
+ uint32_t byte_offset = 0;
+ int32_t shm_id = 1;
+ ClientDiscardableHandle client_handle(buffer, byte_offset, shm_id);
+ EXPECT_EQ(client_handle.shm_id(), shm_id);
+ EXPECT_TRUE(client_handle.IsLockedForTesting());
+
+ ServiceDiscardableHandle service_handle(buffer, byte_offset, shm_id);
+ EXPECT_EQ(service_handle.shm_id(), shm_id);
+ EXPECT_TRUE(service_handle.IsLockedForTesting());
+
+ for (int i = 1; i < 10; ++i) {
+ EXPECT_TRUE(client_handle.IsLockedForTesting());
+ EXPECT_TRUE(service_handle.IsLockedForTesting());
+ EXPECT_TRUE(client_handle.Lock());
+ }
+
+ for (int i = 0; i < 10; ++i) {
+ EXPECT_TRUE(client_handle.IsLockedForTesting());
+ EXPECT_TRUE(service_handle.IsLockedForTesting());
+ service_handle.Unlock();
+ }
+
+ EXPECT_FALSE(client_handle.IsLockedForTesting());
+ EXPECT_FALSE(service_handle.IsLockedForTesting());
+}
+
+TEST(DiscardableHandleTest, Suballocations) {
+ static const int32_t num_elements = 10;
+ scoped_refptr<Buffer> buffer = MakeBufferForTesting(num_elements);
+
+ std::vector<ClientDiscardableHandle> client_handles;
+ std::vector<ServiceDiscardableHandle> service_handles;
+ for (int32_t i = 0; i < num_elements; ++i) {
+ client_handles.emplace_back(buffer, sizeof(base::subtle::Atomic32) * i,
+ i + 1);
+ EXPECT_EQ(client_handles[i].shm_id(), i + 1);
+ EXPECT_TRUE(client_handles[i].IsLockedForTesting());
+
+ service_handles.emplace_back(buffer, sizeof(base::subtle::Atomic32) * i,
+ i + 1);
+ EXPECT_EQ(service_handles[i].shm_id(), i + 1);
+ EXPECT_TRUE(service_handles[i].IsLockedForTesting());
+ }
+
+ for (int32_t i = 0; i < num_elements; i += 2) {
+ service_handles[i].Unlock();
+ }
+
+ for (int32_t i = 1; i < num_elements; i += 2) {
+ client_handles[i].Lock();
+ }
+
+ for (int32_t i = 0; i < num_elements; ++i) {
+ if (i % 2) {
+ EXPECT_TRUE(client_handles[i].IsLockedForTesting());
+ EXPECT_TRUE(service_handles[i].IsLockedForTesting());
+ } else {
+ EXPECT_FALSE(client_handles[i].IsLockedForTesting());
+ EXPECT_FALSE(service_handles[i].IsLockedForTesting());
+ }
+ }
+}
+
+} // namespace gpu