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