• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/vulkan/push_constant.h"
16 
17 #include <algorithm>
18 #include <cassert>
19 #include <limits>
20 
21 #include "src/make_unique.h"
22 #include "src/vulkan/command_buffer.h"
23 #include "src/vulkan/device.h"
24 
25 namespace amber {
26 namespace vulkan {
27 
PushConstant(Device * device)28 PushConstant::PushConstant(Device* device)
29     : device_(device), buffer_(MakeUnique<Buffer>()) {}
30 
31 PushConstant::~PushConstant() = default;
32 
GetVkPushConstantRange()33 VkPushConstantRange PushConstant::GetVkPushConstantRange() {
34   if (push_constant_data_.empty())
35     return VkPushConstantRange();
36 
37   auto it =
38       std::min_element(push_constant_data_.begin(), push_constant_data_.end(),
39                        [](const BufferInput& a, const BufferInput& b) {
40                          return a.offset < b.offset;
41                        });
42   assert(it != push_constant_data_.end());
43 
44   uint32_t first_offset = it->offset;
45 
46   it = std::max_element(
47       push_constant_data_.begin(), push_constant_data_.end(),
48       [](const BufferInput& a, const BufferInput& b) {
49         return a.offset + static_cast<uint32_t>(a.buffer->GetSizeInBytes()) <
50                b.offset + static_cast<uint32_t>(b.buffer->GetSizeInBytes());
51       });
52   assert(it != push_constant_data_.end());
53 
54   uint32_t size_in_bytes = it->offset +
55                            static_cast<uint32_t>(it->buffer->GetSizeInBytes()) -
56                            first_offset;
57 
58   VkPushConstantRange range = VkPushConstantRange();
59   range.stageFlags = VK_SHADER_STAGE_ALL;
60 
61   // Based on Vulkan spec, range.offset must be multiple of 4.
62   range.offset = (first_offset / 4U) * 4U;
63 
64   // Based on Vulkan spec, range.size must be multiple of 4.
65   assert(size_in_bytes + 3U <= std::numeric_limits<uint32_t>::max());
66   range.size = ((size_in_bytes + 3U) / 4U) * 4U;
67 
68   return range;
69 }
70 
RecordPushConstantVkCommand(CommandBuffer * command,VkPipelineLayout pipeline_layout)71 Result PushConstant::RecordPushConstantVkCommand(
72     CommandBuffer* command,
73     VkPipelineLayout pipeline_layout) {
74   if (push_constant_data_.empty())
75     return {};
76 
77   auto push_const_range = GetVkPushConstantRange();
78   if (push_const_range.offset + push_const_range.size >
79       device_->GetMaxPushConstants()) {
80     return Result(
81         "PushConstant::RecordPushConstantVkCommand push constant size in bytes "
82         "exceeds maxPushConstantsSize of VkPhysicalDeviceLimits");
83   }
84 
85   for (const auto& data : push_constant_data_) {
86     Result r = UpdateMemoryWithInput(data);
87     if (!r.IsSuccess())
88       return r;
89   }
90 
91   // Based on spec, offset and size in bytes of push constant must
92   // be multiple of 4.
93   if (push_const_range.offset % 4U != 0)
94     return Result("PushConstant:: Offset must be a multiple of 4");
95   if (push_const_range.size % 4U != 0)
96     return Result("PushConstant:: Size must be a multiple of 4");
97 
98   device_->GetPtrs()->vkCmdPushConstants(
99       command->GetVkCommandBuffer(), pipeline_layout, VK_SHADER_STAGE_ALL,
100       push_const_range.offset, push_const_range.size,
101       buffer_->GetValues<uint8_t*>() + push_const_range.offset);
102   return {};
103 }
104 
AddBuffer(const Buffer * buffer,uint32_t offset)105 Result PushConstant::AddBuffer(const Buffer* buffer, uint32_t offset) {
106   push_constant_data_.emplace_back();
107   push_constant_data_.back().offset = offset;
108   push_constant_data_.back().buffer = buffer;
109   return {};
110 }
111 
UpdateMemoryWithInput(const BufferInput & input)112 Result PushConstant::UpdateMemoryWithInput(const BufferInput& input) {
113   if (static_cast<size_t>(input.offset) >= device_->GetMaxPushConstants()) {
114     return Result(
115         "Vulkan: UpdateMemoryWithInput BufferInput offset exceeds memory size");
116   }
117 
118   if (input.buffer->GetSizeInBytes() >
119       (device_->GetMaxPushConstants() - input.offset)) {
120     return Result(
121         "Vulkan: UpdateMemoryWithInput BufferInput offset + size_in_bytes "
122         " exceeds memory size");
123   }
124 
125   if (!buffer_->GetFormat()) {
126     buffer_->SetFormat(input.buffer->GetFormat());
127   } else if (!buffer_->GetFormat()->Equal(input.buffer->GetFormat())) {
128     return Result("Vulkan: push constants must all have the same format");
129   }
130 
131   buffer_->SetDataFromBuffer(input.buffer, input.offset);
132 
133   return {};
134 }
135 
136 }  // namespace vulkan
137 }  // namespace amber
138