Compute Library
 22.11
impl.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2021-2022 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 #ifndef SRC_CORE_SVE_KERNELS_ELEMENTWISE_QUANTIZED_LIST_H
25 #define SRC_CORE_SVE_KERNELS_ELEMENTWISE_QUANTIZED_LIST_H
26 
28 namespace arm_compute
29 {
30 namespace cpu
31 {
32 using namespace arm_compute::wrapper;
33 
34 inline svfloat32x4_t load_quantized(const int8_t *ptr, svbool_t pg, const svint32_t &offset, const svfloat32_t &scale)
35 {
36  auto x = svld1(pg, ptr);
37 
38  const auto widened = svcreate4(
39  svmovlb(svmovlb(x)),
40  svmovlt(svmovlb(x)),
41  svmovlb(svmovlt(x)),
42  svmovlt(svmovlt(x)));
43 
44  pg = svptrue_b8();
45 
46  return svcreate4(
47  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svget4(widened, 0), offset)), scale),
48  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svget4(widened, 1), offset)), scale),
49  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svget4(widened, 2), offset)), scale),
50  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svget4(widened, 3), offset)), scale));
51 }
52 
53 inline svfloat32x4_t load_quantized(const uint8_t *ptr, svbool_t pg, const svint32_t &offset, const svfloat32_t &scale)
54 {
55  auto x = svld1(pg, ptr);
56 
57  //vprint(x);
58 
59  const auto widened = svcreate4(
60  svmovlb(svmovlb(x)),
61  svmovlt(svmovlb(x)),
62  svmovlb(svmovlt(x)),
63  svmovlt(svmovlt(x)));
64 
65  pg = svptrue_b8();
66 
67  return svcreate4(
68  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svreinterpret_s32(svget4(widened, 0)), offset)), scale),
69  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svreinterpret_s32(svget4(widened, 1)), offset)), scale),
70  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svreinterpret_s32(svget4(widened, 2)), offset)), scale),
71  svmul_z(pg, svcvt_f32_z(pg, svsub_z(pg, svreinterpret_s32(svget4(widened, 3)), offset)), scale));
72 }
73 
74 inline void store_quantized(uint8_t *ptr, svbool_t pg, svfloat32x4_t data, const svint32_t &offset, const svfloat32_t &inv_scale)
75 {
76  const auto quantized = svcreate4(
77  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 0), inv_scale))), offset),
78  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 1), inv_scale))), offset),
79  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 2), inv_scale))), offset),
80  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 3), inv_scale))), offset));
81 
82  const auto narrowed_bottom = svqxtunt(svqxtunb(svget4(quantized, 0)), svget4(quantized, 1));
83  const auto narrowed_top = svqxtunt(svqxtunb(svget4(quantized, 2)), svget4(quantized, 3));
84  const auto narrowed = svqxtnt(svqxtnb(narrowed_bottom), narrowed_top);
85  svst1(pg, ptr, narrowed);
86 }
87 
88 inline void store_quantized(int8_t *ptr, svbool_t pg, svfloat32x4_t data, const svint32_t &offset, const svfloat32_t &inv_scale)
89 {
90  const auto quantized = svcreate4(
91  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 0), inv_scale))), offset),
92  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 1), inv_scale))), offset),
93  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 2), inv_scale))), offset),
94  svadd_z(pg, svcvt_s32_z(pg, svrinta_z(pg, svmul_z(pg, svget4(data, 3), inv_scale))), offset));
95 
96  const auto narrowed_bottom = svqxtnt(svqxtnb(svget4(quantized, 0)), svget4(quantized, 1));
97  const auto narrowed_top = svqxtnt(svqxtnb(svget4(quantized, 2)), svget4(quantized, 3));
98  const auto narrowed = svqxtnt(svqxtnb(narrowed_bottom), narrowed_top);
99 
100  svst1(pg, ptr, narrowed);
101 }
102 
103 template <typename ScalarType>
104 void elementwise_arithmetic_quantized_op(const ITensor *in1, const ITensor *in2, ITensor *out, ArithmeticOperation op, const Window &window)
105 {
106  const auto all_true_pg = wrapper::svptrue<ScalarType>();
107 
108  // Create input windows
109  Window input1_win = window.broadcast_if_dimension_le_one(in1->info()->tensor_shape());
110  Window input2_win = window.broadcast_if_dimension_le_one(in2->info()->tensor_shape());
111 
112  // Clear X Dimension on execution window as we handle manually
113  Window win = window;
114  win.set(Window::DimX, Window::Dimension(0, 1, 1));
115 
116  const auto window_start_x = static_cast<int>(window.x().start());
117  const auto window_end_x = static_cast<int>(window.x().end());
118  const bool is_broadcast_across_x = in1->info()->tensor_shape().x() != in2->info()->tensor_shape().x();
119 
120  const auto output_voffset = svdup_n(out->info()->quantization_info().uniform().offset);
121  const auto output_vscale = svdup_n(1.f / out->info()->quantization_info().uniform().scale);
122 
123  if(is_broadcast_across_x)
124  {
125  const bool is_broadcast_input_2 = input2_win.x().step() == 0;
126  Window broadcast_win = is_broadcast_input_2 ? input2_win : input1_win;
127  Window non_broadcast_win = !is_broadcast_input_2 ? input2_win : input1_win;
128  const ITensor *broadcast_tensor = is_broadcast_input_2 ? in2 : in1;
129  const ITensor *non_broadcast_tensor = !is_broadcast_input_2 ? in2 : in1;
130 
131  const auto non_broadcast_qinfo = is_broadcast_input_2 ? in1->info()->quantization_info() : in2->info()->quantization_info();
132  const auto broadcast_qinfo = is_broadcast_input_2 ? in2->info()->quantization_info() : in1->info()->quantization_info();
133 
134  const auto non_broadcast_voffset = svdup_n(non_broadcast_qinfo.uniform().offset);
135  const auto non_broadcast_vscale = svdup_n(non_broadcast_qinfo.uniform().scale);
136 
137  // Clear X Dimension on execution window as we handle manually
138  non_broadcast_win.set(Window::DimX, Window::Dimension(0, 1, 1));
139 
140  Iterator broadcast_input(broadcast_tensor, broadcast_win);
141  Iterator non_broadcast_input(non_broadcast_tensor, non_broadcast_win);
142  Iterator output(out, win);
143 
144  execute_window_loop(win, [&](const Coordinates &)
145  {
146  auto output_ptr = reinterpret_cast<ScalarType *>(output.ptr());
147  const auto non_broadcast_input_ptr = reinterpret_cast<const ScalarType *>(non_broadcast_input.ptr());
148  const ScalarType broadcast_value = *reinterpret_cast<const ScalarType *>(broadcast_input.ptr());
149  const float broadcast_value_f = Qasymm8QuantizationHelper<ScalarType>::dequantize(broadcast_value, broadcast_qinfo);
150  const auto in2 = svcreate4(svdup_n(broadcast_value_f), svdup_n(broadcast_value_f), svdup_n(broadcast_value_f), svdup_n(broadcast_value_f));
151 
152  int x = window_start_x;
153 
154  svbool_t pg = wrapper::svwhilelt<ScalarType>(x, window_end_x);
155  do
156  {
157  const auto in1 = load_quantized(non_broadcast_input_ptr + x, pg, non_broadcast_voffset, non_broadcast_vscale);
158 
159  svfloat32x4_t result{};
160 
161  if(!is_broadcast_input_2)
162  {
163  result = svcreate4(
164  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in2, 0), svget4(in1, 0), op),
165  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in2, 1), svget4(in1, 1), op),
166  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in2, 2), svget4(in1, 2), op),
167  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in2, 3), svget4(in1, 3), op));
168  }
169  else
170  {
171  result = svcreate4(
172  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 0), svget4(in2, 0), op),
173  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 1), svget4(in2, 1), op),
174  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 2), svget4(in2, 2), op),
175  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 3), svget4(in2, 3), op));
176  }
177 
178  store_quantized(output_ptr + x, pg, result, output_voffset, output_vscale);
179 
180  x += wrapper::svcnt<ScalarType>();
181  pg = wrapper::svwhilelt<ScalarType>(x, window_end_x);
182  }
183  while(svptest_any(all_true_pg, pg));
184  },
185  broadcast_input, non_broadcast_input, output);
186  }
187  else
188  {
189  // Clear X Dimension on execution window as we handle manually
190  input1_win.set(Window::DimX, Window::Dimension(0, 1, 1));
191  input2_win.set(Window::DimX, Window::Dimension(0, 1, 1));
192 
193  Iterator input1(in1, input1_win);
194  Iterator input2(in2, input2_win);
195  Iterator output(out, win);
196 
197  const auto in1_voffset = svdup_n(in1->info()->quantization_info().uniform().offset);
198  const auto in1_vscale = svdup_n(in1->info()->quantization_info().uniform().scale);
199 
200  const auto in2_voffset = svdup_n(in2->info()->quantization_info().uniform().offset);
201  const auto in2_vscale = svdup_n(in2->info()->quantization_info().uniform().scale);
202 
203  execute_window_loop(win, [&](const Coordinates &)
204  {
205  auto output_ptr = reinterpret_cast<ScalarType *>(output.ptr());
206  const auto input1_ptr = reinterpret_cast<const ScalarType *>(input1.ptr());
207  const auto input2_ptr = reinterpret_cast<const ScalarType *>(input2.ptr());
208 
209  int x = window_start_x;
210 
211  svbool_t pg = wrapper::svwhilelt<ScalarType>(x, window_end_x);
212  do
213  {
214  const auto in1 = load_quantized(input1_ptr + x, pg, in1_voffset, in1_vscale);
215  const auto in2 = load_quantized(input2_ptr + x, pg, in2_voffset, in2_vscale);
216 
217  const auto result = svcreate4(
218  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 0), svget4(in2, 0), op),
219  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 1), svget4(in2, 1), op),
220  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 2), svget4(in2, 2), op),
221  elementwise_arithmetic_op<svfloat32_t>(pg, svget4(in1, 3), svget4(in2, 3), op));
222 
223  store_quantized(output_ptr + x, pg, result, output_voffset, output_vscale);
224 
225  x += wrapper::svcnt<ScalarType>();
226  pg = wrapper::svwhilelt<ScalarType>(x, window_end_x);
227  }
228  while(svptest_any(all_true_pg, pg));
229  },
230  input1, input2, output);
231  }
232 }
233 
234 template <typename InputScalarType, typename OutputScalarType = uint8_t>
235 void elementwise_comparison_quantized_op(const ITensor *in1, const ITensor *in2, ITensor *out, ComparisonOperation op, const Window &window)
236 {
237  static_assert(sizeof(InputScalarType) >= sizeof(OutputScalarType), "input data type's width should be equal to or greater than output data type's width");
238 
239  using OutputVectorType = typename wrapper::traits::sve_vector<OutputScalarType>::type;
240  const auto all_true_pg = wrapper::svptrue<InputScalarType>();
241 
242  // Create input windows
243  Window input1_win = window.broadcast_if_dimension_le_one(in1->info()->tensor_shape());
244  Window input2_win = window.broadcast_if_dimension_le_one(in2->info()->tensor_shape());
245 
246  // Clear X Dimension on execution window as we handle manually
247  Window win = window;
248  win.set(Window::DimX, Window::Dimension(0, 1, 1));
249 
250  const auto window_start_x = static_cast<int>(window.x().start());
251  const auto window_end_x = static_cast<int>(window.x().end());
252  const bool is_broadcast_across_x = in1->info()->tensor_shape().x() != in2->info()->tensor_shape().x();
253 
254  if(is_broadcast_across_x)
255  {
256  const bool is_broadcast_input_2 = input2_win.x().step() == 0;
257  Window broadcast_win = is_broadcast_input_2 ? input2_win : input1_win;
258  Window non_broadcast_win = !is_broadcast_input_2 ? input2_win : input1_win;
259  const ITensor *broadcast_tensor = is_broadcast_input_2 ? in2 : in1;
260  const ITensor *non_broadcast_tensor = !is_broadcast_input_2 ? in2 : in1;
261 
262  const auto non_broadcast_qinfo = is_broadcast_input_2 ? in1->info()->quantization_info() : in2->info()->quantization_info();
263  const auto broadcast_qinfo = is_broadcast_input_2 ? in2->info()->quantization_info() : in1->info()->quantization_info();
264 
265  const auto non_broadcast_voffset = svdup_n(non_broadcast_qinfo.uniform().offset);
266  const auto non_broadcast_vscale = svdup_n(non_broadcast_qinfo.uniform().scale);
267 
268  // Clear X Dimension on execution window as we handle manually
269  non_broadcast_win.set(Window::DimX, Window::Dimension(0, 1, 1));
270 
271  Iterator broadcast_input(broadcast_tensor, broadcast_win);
272  Iterator non_broadcast_input(non_broadcast_tensor, non_broadcast_win);
273  Iterator output(out, win);
274 
275  execute_window_loop(win, [&](const Coordinates &)
276  {
277  auto output_ptr = reinterpret_cast<OutputScalarType *>(output.ptr());
278  const auto non_broadcast_input_ptr = reinterpret_cast<const InputScalarType *>(non_broadcast_input.ptr());
279  const InputScalarType broadcast_value = *reinterpret_cast<const InputScalarType *>(broadcast_input.ptr());
280  const float broadcast_value_f = Qasymm8QuantizationHelper<InputScalarType>::dequantize(broadcast_value, broadcast_qinfo);
281  const auto in2 = svcreate4(svdup_n(broadcast_value_f), svdup_n(broadcast_value_f), svdup_n(broadcast_value_f), svdup_n(broadcast_value_f));
282 
283  int x = window_start_x;
284 
285  svbool_t pg = wrapper::svwhilelt<InputScalarType>(x, window_end_x);
286  do
287  {
288  const auto in1 = load_quantized(non_broadcast_input_ptr + x, pg, non_broadcast_voffset, non_broadcast_vscale);
289 
290  svuint8x4_t result{};
291 
292  if(!is_broadcast_input_2)
293  {
294  result = svcreate4(
295  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in2, 0), svget4(in1, 0), op),
296  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in2, 1), svget4(in1, 1), op),
297  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in2, 2), svget4(in1, 2), op),
298  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in2, 3), svget4(in1, 3), op));
299  }
300  else
301  {
302  result = svcreate4(
303  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 0), svget4(in2, 0), op),
304  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 1), svget4(in2, 1), op),
305  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 2), svget4(in2, 2), op),
306  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 3), svget4(in2, 3), op));
307  }
308 
309  const auto zipped_bottom = svzip1(svget4(result, 0), svget4(result, 1));
310  const auto zipped_top = svzip1(svget4(result, 2), svget4(result, 3));
311  const auto zipped = svzip1(zipped_bottom, zipped_top);
312  svst1(pg, output_ptr + x, zipped);
313 
314  x += wrapper::svcnt<InputScalarType>();
315  pg = wrapper::svwhilelt<InputScalarType>(x, window_end_x);
316  }
317  while(svptest_any(all_true_pg, pg));
318  },
319  broadcast_input, non_broadcast_input, output);
320  }
321  else
322  {
323  // Clear X Dimension on execution window as we handle manually
324  input1_win.set(Window::DimX, Window::Dimension(0, 1, 1));
325  input2_win.set(Window::DimX, Window::Dimension(0, 1, 1));
326 
327  Iterator input1(in1, input1_win);
328  Iterator input2(in2, input2_win);
329  Iterator output(out, win);
330 
331  const auto in1_voffset = svdup_n(in1->info()->quantization_info().uniform().offset);
332  const auto in1_vscale = svdup_n(in1->info()->quantization_info().uniform().scale);
333 
334  const auto in2_voffset = svdup_n(in2->info()->quantization_info().uniform().offset);
335  const auto in2_vscale = svdup_n(in2->info()->quantization_info().uniform().scale);
336 
337  execute_window_loop(win, [&](const Coordinates &)
338  {
339  auto output_ptr = reinterpret_cast<OutputScalarType *>(output.ptr());
340  const auto input1_ptr = reinterpret_cast<const InputScalarType *>(input1.ptr());
341  const auto input2_ptr = reinterpret_cast<const InputScalarType *>(input2.ptr());
342 
343  int x = window_start_x;
344 
345  svbool_t pg = wrapper::svwhilelt<InputScalarType>(x, window_end_x);
346  do
347  {
348  const auto in1 = load_quantized(input1_ptr + x, pg, in1_voffset, in1_vscale);
349  const auto in2 = load_quantized(input2_ptr + x, pg, in2_voffset, in2_vscale);
350  const auto result = svcreate4(
351  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 0), svget4(in2, 0), op),
352  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 1), svget4(in2, 1), op),
353  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 2), svget4(in2, 2), op),
354  elementwise_comparison_op<svfloat32_t, OutputVectorType>(pg, svget4(in1, 3), svget4(in2, 3), op));
355 
356  const auto zipped_bottom = svzip1(svget4(result, 0), svget4(result, 1));
357  const auto zipped_top = svzip1(svget4(result, 2), svget4(result, 3));
358  const auto zipped = svzip1(zipped_bottom, zipped_top);
359  svst1(pg, output_ptr + x, zipped);
360 
361  x += wrapper::svcnt<InputScalarType>();
362  pg = wrapper::svwhilelt<InputScalarType>(x, window_end_x);
363  }
364  while(svptest_any(all_true_pg, pg));
365  },
366  input1, input2, output);
367  }
368 }
369 } // namespace cpu
370 } // namespace arm_compute
371 
372 #endif /* SRC_CORE_SVE_KERNELS_ELEMENTWISE_QUANTIZED_LIST_H */
__global uchar * offset(const Image *img, int x, int y)
Get the pointer position of a Image.
Definition: helpers.h:1084
ArithmeticOperation
Available element-wise operations.
Definition: Types.h:489
float32x4x4_t load_quantized(const uint8_t *input1_ptr, const int32x4_t &offset, const float32x4_t &scale)
Definition: impl.h:482
constexpr int step() const
Return the step of the dimension.
Definition: Window.h:107
Describe one of the image&#39;s dimensions with a start, end and step.
Definition: Window.h:79
void elementwise_arithmetic_quantized_op(const ITensor *in1, const ITensor *in2, ITensor *out, ArithmeticOperation op, const Window &window)
Definition: impl.h:104
decltype(strategy::transforms) typedef type
Interface for CPU tensor.
Definition: ITensor.h:36
Copyright (c) 2017-2022 Arm Limited.
T x() const
Alias to access the size of the first dimension.
Definition: Dimensions.h:87
static constexpr size_t DimX
Alias for dimension 0 also known as X dimension.
Definition: Window.h:43
virtual const TensorShape & tensor_shape() const =0
Size for each dimension of the tensor.
void elementwise_comparison_quantized_op(const ITensor *in1, const ITensor *in2, ITensor *out, ComparisonOperation op, const Window &window)
Definition: impl.h:235
Coordinates of an item.
Definition: Coordinates.h:37
UniformQuantizationInfo uniform() const
Return per layer quantization info.
virtual ITensorInfo * info() const =0
Interface to be implemented by the child class to return the tensor&#39;s metadata.
constexpr uint8_t * ptr() const
Return a pointer to the current pixel.
Definition: Helpers.inl:139
ComparisonOperation
Supported comparison operations.
Definition: Types.h:173
void set(size_t dimension, const Dimension &dim)
Set the values of a given dimension.
Definition: Window.inl:49
Window broadcast_if_dimension_le_one(const TensorShape &shape) const
Don&#39;t advance in the dimension where shape is less equal to 1.
Definition: Window.inl:120
virtual QuantizationInfo quantization_info() const =0
Get the quantization settings (scale and offset) of the tensor.
static float dequantize(QUANTIZED_TYPE value, const UniformQuantizationInfo &qinfo)
Dequantize a value given a 8-bit asymmetric quantization scheme.
void execute_window_loop(const Window &w, L &&lambda_function, Ts &&... iterators)
Iterate through the passed window, automatically adjusting the iterators and calling the lambda_funct...
Definition: Helpers.inl:77
constexpr int end() const
Return the end of the dimension.
Definition: Window.h:102
Iterator updated by execute_window_loop for each window element.
Definition: Helpers.h:46
constexpr int start() const
Return the start of the dimension.
Definition: Window.h:97
Describe a multidimensional execution window.
Definition: Window.h:39
void store_quantized(uint8_t *output_ptr, const uint32x4x4_t &out)
Definition: impl.h:512
constexpr const Dimension & x() const
Alias to access the first dimension of the window.
Definition: Window.h:159