1 /*
2 * Copyright (c) 2016-2021 Arm Limited.
3 *
4 * SPDX-License-Identifier: MIT
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24 #include "src/core/NEON/kernels/NEFillBorderKernel.h"
25
26 #include "arm_compute/core/Error.h"
27 #include "arm_compute/core/Helpers.h"
28 #include "arm_compute/core/ITensor.h"
29 #include "arm_compute/core/TensorInfo.h"
30 #include "arm_compute/core/Types.h"
31 #include "arm_compute/core/Validate.h"
32 #include "arm_compute/core/Window.h"
33 #include "src/core/NEON/kernels/NEFillBorderKernel.h"
34 #include "src/core/helpers/WindowHelpers.h"
35
36 namespace arm_compute
37 {
38 namespace
39 {
fill_constant_value_single_channel_special(ITensor * tensor,const Window & window,unsigned int right,unsigned int bottom,const PixelValue & constant_border_value)40 inline void fill_constant_value_single_channel_special(ITensor *tensor, const Window &window, unsigned int right, unsigned int bottom, const PixelValue &constant_border_value)
41 {
42 float border_value;
43 constant_border_value.get(border_value);
44 uint8_t *const start_valid_region = tensor->ptr_to_element(tensor->info()->valid_region().anchor);
45 const size_t width = tensor->info()->valid_region().shape[0];
46 const size_t height = tensor->info()->valid_region().shape[1];
47 const int stridey = tensor->info()->strides_in_bytes()[1];
48
49 // Left and right border
50 Window vertical(window);
51 vertical.set(Window::DimY, Window::Dimension(0, height, 1));
52
53 Iterator vertical_it(tensor, vertical);
54
55 execute_window_loop(vertical, [&](const Coordinates &)
56 {
57 const auto row_start = reinterpret_cast<float *>(start_valid_region + vertical_it.offset());
58
59 // Fill left and right borders
60 *(row_start - 1) = border_value;
61 std::fill_n(row_start + width, right, border_value);
62 },
63 vertical_it);
64
65 // Top and bottom border
66 Iterator plane_it(tensor, window);
67
68 // Iterate over all XY planes
69 execute_window_loop(window, [&](const Coordinates &)
70 {
71 uint8_t *base_addr = start_valid_region + plane_it.offset();
72 // Top border
73 const auto row_start = reinterpret_cast<float *>(base_addr - stridey);
74 // Fill top rows including left/right borders
75 std::fill_n(row_start - 1, 1 + width + right, border_value);
76
77 // Bottom border
78 const unsigned low_border_size = height + bottom;
79 for(unsigned int i = height; i < low_border_size; ++i)
80 {
81 const auto row_start = reinterpret_cast<float *>(base_addr + i * stridey);
82
83 // Fill bottom rows including left/right borders
84 std::fill_n(row_start - 1, 1 + width + right, border_value);
85 }
86 },
87 plane_it);
88 }
89 } // namespace
90
NEFillBorderKernel()91 NEFillBorderKernel::NEFillBorderKernel()
92 : _tensor(nullptr), _border_size(0), _mode(BorderMode::UNDEFINED), _constant_border_value(static_cast<float>(0.f))
93 {
94 }
95
configure(ITensor * tensor,BorderSize border_size,BorderMode border_mode,const PixelValue & constant_border_value)96 void NEFillBorderKernel::configure(ITensor *tensor, BorderSize border_size, BorderMode border_mode, const PixelValue &constant_border_value)
97 {
98 ARM_COMPUTE_ERROR_ON_NULLPTR(tensor);
99 _tensor = tensor;
100 configure(tensor->info(), border_size, border_mode, constant_border_value);
101 }
102
configure(ITensorInfo * tensor,BorderSize border_size,BorderMode border_mode,const PixelValue & constant_border_value)103 void NEFillBorderKernel::configure(ITensorInfo *tensor, BorderSize border_size, BorderMode border_mode, const PixelValue &constant_border_value)
104 {
105 ARM_COMPUTE_ERROR_ON_NULLPTR(tensor);
106 //Note: ARM_COMPUTE_RETURN_ERROR_ON_CPU_F16_UNSUPPORTED(input) is not needed here as this kernel doesn't use CPU FP16 instructions.
107 ARM_COMPUTE_ERROR_ON(tensor->data_type() == DataType::UNKNOWN);
108
109 _border_size = border_size;
110 _mode = border_mode;
111 _constant_border_value = constant_border_value;
112
113 _border_size.limit(tensor->padding());
114
115 Window win;
116 win.set(Window::DimX, Window::Dimension(0, 1, 1));
117 win.set(Window::DimY, Window::Dimension(0, 1, 1));
118 win.use_tensor_dimensions(tensor->tensor_shape(), Window::DimZ);
119 INEKernel::configure(win);
120 }
121
run(const Window & window,const ThreadInfo & info)122 void NEFillBorderKernel::run(const Window &window, const ThreadInfo &info)
123 {
124 ARM_COMPUTE_UNUSED(info);
125
126 // If there is no border: early exit
127 if(_border_size.empty())
128 {
129 return;
130 }
131
132 ARM_COMPUTE_ERROR_ON_UNCONFIGURED_KERNEL(this);
133 ARM_COMPUTE_ERROR_ON_INVALID_SUBWINDOW(INEKernel::window(), window);
134
135 switch(_mode)
136 {
137 case BorderMode::CONSTANT:
138 {
139 if(_border_size.left == 1 && _border_size.top == 1 && _tensor->info()->data_type() == DataType::F32)
140 {
141 fill_constant_value_single_channel_special(_tensor, window, _border_size.right, _border_size.bottom, _constant_border_value);
142 }
143 else
144 {
145 fill_constant_value_single_channel(window);
146 }
147 break;
148 }
149 case BorderMode::REPLICATE:
150 {
151 fill_replicate_single_channel(window);
152 break;
153 }
154 case BorderMode::UNDEFINED:
155 break; // Nothing to do here
156 default:
157 ARM_COMPUTE_ERROR("Unknown border mode");
158 }
159 }
160
run_op(ITensorPack & tensors,const Window & window,const ThreadInfo & info)161 void NEFillBorderKernel::run_op(ITensorPack &tensors, const Window &window, const ThreadInfo &info)
162 {
163 _tensor = tensors.get_tensor(TensorType::ACL_SRC_DST);
164 run(window, info);
165 }
166
fill_replicate_single_channel(const Window & window)167 void NEFillBorderKernel::fill_replicate_single_channel(const Window &window)
168 {
169 uint8_t *const start_valid_region = _tensor->ptr_to_element(_tensor->info()->valid_region().anchor);
170 const size_t width = _tensor->info()->valid_region().shape[0];
171 const size_t height = _tensor->info()->valid_region().shape[1];
172 const size_t element_size = _tensor->info()->element_size();
173 // Left and right border
174 Window vertical(window);
175 vertical.set(Window::DimY, Window::Dimension(0, height, 1));
176
177 Iterator vertical_it(_tensor, vertical);
178
179 execute_window_loop(vertical, [&](const Coordinates &)
180 {
181 uint8_t *base_addr = start_valid_region + vertical_it.offset();
182 // Fill left and right borders
183 for(unsigned int i = 0; i < _border_size.left; ++i)
184 {
185 std::memcpy(base_addr + static_cast<int>(i - _border_size.left) * element_size, vertical_it.ptr(), element_size);
186 }
187
188 for(unsigned int i = 0; i < _border_size.right; ++i)
189 {
190 std::memcpy(base_addr + (width + i) * element_size, vertical_it.ptr() + (width - 1) * element_size, element_size);
191 }
192 },
193 vertical_it);
194
195 // Top and bottom border
196 Iterator plane_it(_tensor, window);
197
198 // Iterate over all XY planes
199 execute_window_loop(window, [&](const Coordinates &)
200 {
201 uint8_t *base_addr = start_valid_region + plane_it.offset();
202 // Top border
203 for(int i = -_border_size.top; i < 0; ++i)
204 {
205 // Copy top rows including left/right borders
206 std::memcpy(base_addr + i * static_cast<int>(_tensor->info()->strides_in_bytes()[1]) - _border_size.left * element_size,
207 base_addr - _border_size.left * element_size, (_border_size.left + width + _border_size.right) * element_size);
208 }
209
210 // Bottom border
211 for(unsigned int i = height; i < height + _border_size.bottom; ++i)
212 {
213 // Copy bottom rows including left/right borders
214 std::memcpy(base_addr + i * _tensor->info()->strides_in_bytes()[1] - _border_size.left * element_size,
215 base_addr + (height - 1) * _tensor->info()->strides_in_bytes()[1] - _border_size.left * element_size, (_border_size.left + width + _border_size.right) * element_size);
216 }
217 },
218 plane_it);
219 }
220
fill_constant_value_single_channel(const Window & window)221 void NEFillBorderKernel::fill_constant_value_single_channel(const Window &window)
222 {
223 uint8_t *const start_valid_region = _tensor->ptr_to_element(_tensor->info()->valid_region().anchor);
224 const size_t width = _tensor->info()->valid_region().shape[0];
225 const size_t height = _tensor->info()->valid_region().shape[1];
226 const int stridey = _tensor->info()->strides_in_bytes()[1];
227 const size_t element_size = _tensor->info()->element_size();
228
229 // Left and right border
230 Window vertical(window);
231 vertical.set(Window::DimY, Window::Dimension(0, height, 1));
232
233 Iterator vertical_it(_tensor, vertical);
234
235 execute_window_loop(vertical, [&](const Coordinates &)
236 {
237 uint8_t *base_addr = start_valid_region + vertical_it.offset();
238 // Fill left and right borders
239 for(unsigned int i = 0; i < _border_size.left; ++i)
240 {
241 std::memcpy(base_addr + static_cast<int>(i - _border_size.left) * element_size, &_constant_border_value, element_size);
242 }
243
244 for(unsigned int i = 0; i < _border_size.right; ++i)
245 {
246 std::memcpy(base_addr + (width + i) * element_size, &_constant_border_value, element_size);
247 }
248 },
249 vertical_it);
250
251 // Top and bottom border
252 Iterator plane_it(_tensor, window);
253
254 // Iterate over all XY planes
255 execute_window_loop(window, [&](const Coordinates &)
256 {
257 uint8_t *base_addr = start_valid_region + plane_it.offset();
258 // Top border
259 for(int i = -_border_size.top; i < 0; ++i)
260 {
261 // Fill top rows including left/right borders
262 for(unsigned int j = 0; j < (_border_size.left + width + _border_size.right); ++j)
263 {
264 std::memcpy(base_addr + i * stridey + static_cast<int>(j - _border_size.left) * element_size, &_constant_border_value, element_size);
265 }
266 }
267
268 // Bottom border
269 const unsigned low_border_size = height + _border_size.bottom;
270 for(unsigned int i = height; i < low_border_size; ++i)
271 {
272 // Fill bottom rows including left/right borders
273 for(unsigned int j = 0; j < (_border_size.left + width + _border_size.right); ++j)
274 {
275 std::memcpy(base_addr + i * stridey + static_cast<int>(j - _border_size.left) * element_size, &_constant_border_value, element_size);
276 }
277 }
278 },
279 plane_it);
280 }
281 } // namespace arm_compute
282