| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef GPU_COMMAND_BUFFER_SERVICE_COMMON_DECODER_H_ | |
| #define GPU_COMMAND_BUFFER_SERVICE_COMMON_DECODER_H_ | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <array> | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <type_traits> | |
| #include <vector> | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "gpu/command_buffer/common/buffer.h" | |
| #include "gpu/command_buffer/common/cmd_buffer_common.h" | |
| #include "gpu/command_buffer/common/constants.h" | |
| #include "gpu/command_buffer/service/gpu_command_buffer_service_export.h" | |
| namespace gpu { | |
| class CommandBufferServiceBase; | |
| class DecoderClient; | |
| // This class is a helper base class for implementing the common parts of the | |
| // o3d/gl2 command buffer decoder. | |
| class GPU_COMMAND_BUFFER_SERVICE_EXPORT CommonDecoder { | |
| public: | |
| using Error = error::Error; | |
| static constexpr unsigned int kMaxStackDepth = 32; | |
| // A bucket is a buffer to help collect memory across a command buffer. When | |
| // creating a command buffer implementation of an existing API, sometimes that | |
| // API has functions that take a pointer to data. A good example is OpenGL's | |
| // glBufferData. Because the data is separated between client and service, | |
| // there are 2 ways to get this data across. 1 is to put all the data in | |
| // shared memory. The problem with this is the data can be arbitarily large | |
| // and the host OS may not support that much shared memory. Another solution | |
| // is to shuffle memory across a little bit at a time, collecting it on the | |
| // service side and when it is all there then call glBufferData. Buckets | |
| // implement this second solution. Using the common commands, SetBucketSize, | |
| // SetBucketData, SetBucketDataImmediate the client can fill a bucket. It can | |
| // then call a command that uses that bucket (like BufferDataBucket in the | |
| // GLES2 command buffer implementation). | |
| // | |
| // If you are designing an API from scratch you can avoid this need for | |
| // Buckets by making your API always take an offset and a size | |
| // similar to glBufferSubData. | |
| // | |
| // Buckets also help pass strings to/from the service. To return a string of | |
| // arbitary size, the service puts the string in a bucket. The client can | |
| // then query the size of a bucket and request sections of the bucket to | |
| // be passed across shared memory. | |
| class GPU_COMMAND_BUFFER_SERVICE_EXPORT Bucket { | |
| public: | |
| Bucket(); | |
| Bucket(const Bucket&) = delete; | |
| Bucket& operator=(const Bucket&) = delete; | |
| ~Bucket(); | |
| size_t size() const { | |
| return size_; | |
| } | |
| // Gets a span over a section of the bucket, reinterpreted as `count` | |
| // elements of type `T` starting at byte `offset`. Returns an empty span if | |
| // the range is out of bounds. `T` must be trivially copyable. | |
| template <typename T> | |
| base::span<T> GetDataAsSpan(size_t offset, size_t count) { | |
| static_assert(std::is_trivially_copyable_v<T>); | |
| base::span<uint8_t> bytes = GetDataAsByteSpan(offset, count * sizeof(T)); | |
| // The bucket's storage is a raw byte buffer, so reinterpret_span() is the | |
| // intended safe conversion here. It CHECKs alignment and that the byte | |
| // size is a multiple of sizeof(T). | |
| return base::subtle::reinterpret_span<T>(bytes); | |
| } | |
| // Gets a writable byte span over a section of the bucket. Returns an empty | |
| // span if offset or size is out of range. | |
| base::span<uint8_t> GetDataAsByteSpan(size_t offset, size_t size); | |
| // Sets the size of the bucket. | |
| void SetSize(size_t size); | |
| // Sets a part of the bucket. | |
| // Returns false if `offset` or `src.size()` is out of range. | |
| bool SetData(base::span<const volatile uint8_t> src, size_t offset); | |
| // Sets the bucket data from a string. Strings are passed NULL terminated to | |
| // distinguish between empty string and no string. | |
| void SetFromString(const char* str); | |
| // Gets the bucket data as a string. Strings are passed NULL terminated to | |
| // distrinquish between empty string and no string. Returns False if there | |
| // is no string. | |
| bool GetAsString(std::string* str); | |
| // Gets views of the strings stored in the bucket. Returns nullopt if the | |
| // bucket does not contain a valid string array. The returned views are | |
| // invalidated when the bucket's storage is resized or destroyed. | |
| std::optional<std::vector<std::string_view>> GetAsStrings(); | |
| private: | |
| bool OffsetSizeValid(size_t offset, size_t size) const; | |
| size_t size_; | |
| base::HeapArray<uint8_t> data_; | |
| }; | |
| explicit CommonDecoder(DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service); | |
| CommonDecoder(const CommonDecoder&) = delete; | |
| CommonDecoder& operator=(const CommonDecoder&) = delete; | |
| ~CommonDecoder(); | |
| CommandBufferServiceBase* command_buffer_service() const { | |
| return command_buffer_service_; | |
| } | |
| DecoderClient* client() const { return client_; } | |
| // Sets the maximum size for buckets. | |
| void set_max_bucket_size(size_t max_bucket_size) { | |
| max_bucket_size_ = max_bucket_size; | |
| } | |
| // Creates a bucket. If the bucket already exists returns that bucket. | |
| Bucket* CreateBucket(uint32_t bucket_id); | |
| // Gets a bucket. Returns nullptr if the bucket does not exist. | |
| Bucket* GetBucket(uint32_t bucket_id) const; | |
| // Gets the address of shared memory data, given a shared memory ID and an | |
| // offset. Also checks that the size is consistent with the shared memory | |
| // size. | |
| // Parameters: | |
| // shm_id: the id of the shared memory buffer. | |
| // offset: the offset of the data in the shared memory buffer. | |
| // size: the size of the data. | |
| // Returns: | |
| // nullptr if shm_id isn't a valid shared memory buffer ID or if the size | |
| // check fails. Return a pointer to the data otherwise. | |
| void* GetAddressAndCheckSize(unsigned int shm_id, | |
| unsigned int offset, | |
| unsigned int size); | |
| // Typed version of GetAddressAndCheckSize. | |
| template <typename T> | |
| T GetSharedMemoryAs(unsigned int shm_id, unsigned int offset, | |
| unsigned int size) { | |
| return static_cast<T>(GetAddressAndCheckSize(shm_id, offset, size)); | |
| } | |
| template <typename T = uint8_t> | |
| std::optional<base::span<T>> GetSharedMemoryAsSpan(uint32_t shm_id, | |
| uint32_t offset, | |
| size_t element_count) { | |
| // Prevent integer overflow exploits on large element counts. | |
| base::CheckedNumeric<uint32_t> checked_size_in_bytes = | |
| base::CheckedNumeric<size_t>(element_count) * sizeof(T); | |
| uint32_t size_in_bytes; | |
| if (!checked_size_in_bytes.AssignIfValid(&size_in_bytes)) { | |
| return std::nullopt; | |
| } | |
| std::optional<base::span<uint8_t>> byte_span = | |
| GetSharedMemoryAsByteSpan(shm_id, offset, size_in_bytes); | |
| if (!byte_span.has_value()) { | |
| return std::nullopt; | |
| } | |
| // Protect against partial reads or out-of-bounds shared memory access. | |
| if (byte_span->size_bytes() != size_in_bytes) { | |
| return std::nullopt; | |
| } | |
| // Allow valid zero-count draw calls to pass without triggering alignment | |
| // checks. | |
| if (element_count == 0) { | |
| return base::span<T>(); | |
| } | |
| // Prevent undefined behavior from unaligned hardware memory access. | |
| if (reinterpret_cast<uintptr_t>(byte_span->data()) % alignof(T) != 0) { | |
| return std::nullopt; | |
| } | |
| return base::subtle::reinterpret_span<T>(*byte_span); | |
| } | |
| void* GetAddressAndSize(unsigned int shm_id, | |
| unsigned int offset, | |
| unsigned int minimum_size, | |
| unsigned int* size); | |
| template <typename T> | |
| T GetSharedMemoryAndSizeAs(unsigned int shm_id, | |
| unsigned int offset, | |
| unsigned int minimum_size, | |
| unsigned int* size) { | |
| return static_cast<T>( | |
| GetAddressAndSize(shm_id, offset, minimum_size, size)); | |
| } | |
| unsigned int GetSharedMemorySize(unsigned int shm_id, unsigned int offset); | |
| // Get the actual shared memory buffer. | |
| scoped_refptr<gpu::Buffer> GetSharedMemoryBuffer(unsigned int shm_id); | |
| protected: | |
| // Executes a common command. | |
| // Parameters: | |
| // command: the command index. | |
| // arg_count: the number of CommandBufferEntry arguments. | |
| // cmd_data: the command data. | |
| // Returns: | |
| // error::kNoError if no error was found, one of | |
| // error::Error otherwise. | |
| error::Error DoCommonCommand(unsigned int command, | |
| unsigned int arg_count, | |
| const volatile void* cmd_data); | |
| // Gets an name for a common command. | |
| const char* GetCommonCommandName(cmd::CommandId command_id) const; | |
| // Exit the command processing loop to allow context preemption and GPU | |
| // watchdog checks in CommandExecutor(). | |
| virtual void ExitCommandProcessingEarly() {} | |
| private: | |
| // Generate a member function prototype for each command in an automated and | |
| // typesafe way. | |
| #define COMMON_COMMAND_BUFFER_CMD_OP(name) \ | |
| error::Error Handle##name(uint32_t immediate_data_size, \ | |
| const volatile void* data); | |
| COMMON_COMMAND_BUFFER_CMDS(COMMON_COMMAND_BUFFER_CMD_OP) | |
| #undef COMMON_COMMAND_BUFFER_CMD_OP | |
| std::optional<base::span<uint8_t>> GetSharedMemoryAsByteSpan( | |
| uint32_t shm_id, | |
| uint32_t offset, | |
| uint32_t size_in_bytes); | |
| raw_ptr<CommandBufferServiceBase, DanglingUntriaged> command_buffer_service_; | |
| raw_ptr<DecoderClient, DanglingUntriaged> client_; | |
| size_t max_bucket_size_; | |
| using BucketMap = std::map<uint32_t, std::unique_ptr<Bucket>>; | |
| BucketMap buckets_; | |
| using CmdHandler = Error (CommonDecoder::*)(uint32_t immediate_data_size, | |
| const volatile void* data); | |
| // A struct to hold info about each command. | |
| struct CommandInfo { | |
| CmdHandler cmd_handler; | |
| uint8_t arg_flags; // How to handle the arguments for this command | |
| uint8_t cmd_flags; // How to handle this command | |
| uint16_t arg_count; // How many arguments are expected for this command. | |
| }; | |
| // A table of CommandInfo for all the commands. | |
| static const std::array<CommandInfo, cmd::kNumCommands> command_info; | |
| }; | |
| } // namespace gpu | |
| #endif // GPU_COMMAND_BUFFER_SERVICE_COMMON_DECODER_H_ |