// Copyright 2019 The Amber Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/vulkan/push_constant.h" #include #include #include #include "src/make_unique.h" #include "src/vulkan/command_buffer.h" #include "src/vulkan/device.h" namespace amber { namespace vulkan { PushConstant::PushConstant(Device* device) : device_(device), buffer_(MakeUnique()) {} PushConstant::~PushConstant() = default; VkPushConstantRange PushConstant::GetVkPushConstantRange() { if (push_constant_data_.empty()) return VkPushConstantRange(); auto it = std::min_element(push_constant_data_.begin(), push_constant_data_.end(), [](const BufferInput& a, const BufferInput& b) { return a.offset < b.offset; }); assert(it != push_constant_data_.end()); uint32_t first_offset = it->offset; it = std::max_element( push_constant_data_.begin(), push_constant_data_.end(), [](const BufferInput& a, const BufferInput& b) { return a.offset + static_cast(a.buffer->GetSizeInBytes()) < b.offset + static_cast(b.buffer->GetSizeInBytes()); }); assert(it != push_constant_data_.end()); uint32_t size_in_bytes = it->offset + static_cast(it->buffer->GetSizeInBytes()) - first_offset; VkPushConstantRange range = VkPushConstantRange(); range.stageFlags = VK_SHADER_STAGE_ALL; // Based on Vulkan spec, range.offset must be multiple of 4. range.offset = (first_offset / 4U) * 4U; // Based on Vulkan spec, range.size must be multiple of 4. assert(size_in_bytes + 3U <= std::numeric_limits::max()); range.size = ((size_in_bytes + 3U) / 4U) * 4U; return range; } Result PushConstant::RecordPushConstantVkCommand( CommandBuffer* command, VkPipelineLayout pipeline_layout) { if (push_constant_data_.empty()) return {}; auto push_const_range = GetVkPushConstantRange(); if (push_const_range.offset + push_const_range.size > device_->GetMaxPushConstants()) { return Result( "PushConstant::RecordPushConstantVkCommand push constant size in bytes " "exceeds maxPushConstantsSize of VkPhysicalDeviceLimits"); } for (const auto& data : push_constant_data_) { Result r = UpdateMemoryWithInput(data); if (!r.IsSuccess()) return r; } // Based on spec, offset and size in bytes of push constant must // be multiple of 4. if (push_const_range.offset % 4U != 0) return Result("PushConstant:: Offset must be a multiple of 4"); if (push_const_range.size % 4U != 0) return Result("PushConstant:: Size must be a multiple of 4"); device_->GetPtrs()->vkCmdPushConstants( command->GetVkCommandBuffer(), pipeline_layout, VK_SHADER_STAGE_ALL, push_const_range.offset, push_const_range.size, buffer_->GetValues() + push_const_range.offset); return {}; } Result PushConstant::AddBuffer(const Buffer* buffer, uint32_t offset) { push_constant_data_.emplace_back(); push_constant_data_.back().offset = offset; push_constant_data_.back().buffer = buffer; return {}; } Result PushConstant::UpdateMemoryWithInput(const BufferInput& input) { if (static_cast(input.offset) >= device_->GetMaxPushConstants()) { return Result( "Vulkan: UpdateMemoryWithInput BufferInput offset exceeds memory size"); } if (input.buffer->GetSizeInBytes() > (device_->GetMaxPushConstants() - input.offset)) { return Result( "Vulkan: UpdateMemoryWithInput BufferInput offset + size_in_bytes " " exceeds memory size"); } if (!buffer_->GetFormat()) { buffer_->SetFormat(input.buffer->GetFormat()); } else if (!buffer_->GetFormat()->Equal(input.buffer->GetFormat())) { return Result("Vulkan: push constants must all have the same format"); } buffer_->SetDataFromBuffer(input.buffer, input.offset); return {}; } } // namespace vulkan } // namespace amber