test_helper.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "gpu/command_buffer/service/test_helper.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_tokenizer.h"
12#include "gpu/command_buffer/common/types.h"
13#include "gpu/command_buffer/service/buffer_manager.h"
14#include "gpu/command_buffer/service/error_state_mock.h"
15#include "gpu/command_buffer/service/gl_utils.h"
16#include "gpu/command_buffer/service/gpu_switches.h"
17#include "gpu/command_buffer/service/program_manager.h"
18#include "gpu/command_buffer/service/texture_manager.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "ui/gl/gl_mock.h"
21
22using ::testing::_;
23using ::testing::DoAll;
24using ::testing::InSequence;
25using ::testing::MatcherCast;
26using ::testing::Pointee;
27using ::testing::Return;
28using ::testing::SetArrayArgument;
29using ::testing::SetArgumentPointee;
30using ::testing::StrEq;
31using ::testing::StrictMock;
32
33namespace gpu {
34namespace gles2 {
35
36// GCC requires these declarations, but MSVC requires they not be present
37#ifndef COMPILER_MSVC
38const GLuint TestHelper::kServiceBlackTexture2dId;
39const GLuint TestHelper::kServiceDefaultTexture2dId;
40const GLuint TestHelper::kServiceBlackTextureCubemapId;
41const GLuint TestHelper::kServiceDefaultTextureCubemapId;
42const GLuint TestHelper::kServiceBlackExternalTextureId;
43const GLuint TestHelper::kServiceDefaultExternalTextureId;
44const GLuint TestHelper::kServiceBlackRectangleTextureId;
45const GLuint TestHelper::kServiceDefaultRectangleTextureId;
46
47const GLint TestHelper::kMaxSamples;
48const GLint TestHelper::kMaxRenderbufferSize;
49const GLint TestHelper::kMaxTextureSize;
50const GLint TestHelper::kMaxCubeMapTextureSize;
51const GLint TestHelper::kNumVertexAttribs;
52const GLint TestHelper::kNumTextureUnits;
53const GLint TestHelper::kMaxTextureImageUnits;
54const GLint TestHelper::kMaxVertexTextureImageUnits;
55const GLint TestHelper::kMaxFragmentUniformVectors;
56const GLint TestHelper::kMaxFragmentUniformComponents;
57const GLint TestHelper::kMaxVaryingVectors;
58const GLint TestHelper::kMaxVaryingFloats;
59const GLint TestHelper::kMaxVertexUniformVectors;
60const GLint TestHelper::kMaxVertexUniformComponents;
61#endif
62
63void TestHelper::SetupTextureInitializationExpectations(
64    ::gfx::MockGLInterface* gl, GLenum target) {
65  InSequence sequence;
66
67  bool needs_initialization = (target != GL_TEXTURE_EXTERNAL_OES);
68  bool needs_faces = (target == GL_TEXTURE_CUBE_MAP);
69
70  static GLuint texture_2d_ids[] = {
71    kServiceBlackTexture2dId,
72    kServiceDefaultTexture2dId };
73  static GLuint texture_cube_map_ids[] = {
74    kServiceBlackTextureCubemapId,
75    kServiceDefaultTextureCubemapId };
76  static GLuint texture_external_oes_ids[] = {
77    kServiceBlackExternalTextureId,
78    kServiceDefaultExternalTextureId };
79  static GLuint texture_rectangle_arb_ids[] = {
80    kServiceBlackRectangleTextureId,
81    kServiceDefaultRectangleTextureId };
82
83  const GLuint* texture_ids = NULL;
84  switch (target) {
85    case GL_TEXTURE_2D:
86      texture_ids = &texture_2d_ids[0];
87      break;
88    case GL_TEXTURE_CUBE_MAP:
89      texture_ids = &texture_cube_map_ids[0];
90      break;
91    case GL_TEXTURE_EXTERNAL_OES:
92      texture_ids = &texture_external_oes_ids[0];
93      break;
94    case GL_TEXTURE_RECTANGLE_ARB:
95      texture_ids = &texture_rectangle_arb_ids[0];
96      break;
97    default:
98      NOTREACHED();
99  }
100
101  int array_size = 2;
102
103  EXPECT_CALL(*gl, GenTextures(array_size, _))
104      .WillOnce(SetArrayArgument<1>(texture_ids,
105                                    texture_ids + array_size))
106          .RetiresOnSaturation();
107  for (int ii = 0; ii < array_size; ++ii) {
108    EXPECT_CALL(*gl, BindTexture(target, texture_ids[ii]))
109        .Times(1)
110        .RetiresOnSaturation();
111    if (needs_initialization) {
112      if (needs_faces) {
113        static GLenum faces[] = {
114          GL_TEXTURE_CUBE_MAP_POSITIVE_X,
115          GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
116          GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
117          GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
118          GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
119          GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
120        };
121        for (size_t ii = 0; ii < arraysize(faces); ++ii) {
122          EXPECT_CALL(*gl, TexImage2D(faces[ii], 0, GL_RGBA, 1, 1, 0, GL_RGBA,
123                                      GL_UNSIGNED_BYTE, _))
124              .Times(1)
125              .RetiresOnSaturation();
126        }
127      } else {
128        EXPECT_CALL(*gl, TexImage2D(target, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
129                                    GL_UNSIGNED_BYTE, _))
130            .Times(1)
131            .RetiresOnSaturation();
132      }
133    }
134  }
135  EXPECT_CALL(*gl, BindTexture(target, 0))
136      .Times(1)
137      .RetiresOnSaturation();
138}
139
140void TestHelper::SetupTextureManagerInitExpectations(
141    ::gfx::MockGLInterface* gl,
142    const char* extensions) {
143  InSequence sequence;
144
145  SetupTextureInitializationExpectations(gl, GL_TEXTURE_2D);
146  SetupTextureInitializationExpectations(gl, GL_TEXTURE_CUBE_MAP);
147
148  bool ext_image_external = false;
149  bool arb_texture_rectangle = false;
150  base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
151  while (t.GetNext()) {
152    if (t.token() == "GL_OES_EGL_image_external") {
153      ext_image_external = true;
154      break;
155    }
156    if (t.token() == "GL_ARB_texture_rectangle") {
157      arb_texture_rectangle = true;
158      break;
159    }
160  }
161
162  if (ext_image_external) {
163    SetupTextureInitializationExpectations(gl, GL_TEXTURE_EXTERNAL_OES);
164  }
165  if (arb_texture_rectangle) {
166    SetupTextureInitializationExpectations(gl, GL_TEXTURE_RECTANGLE_ARB);
167  }
168}
169
170void TestHelper::SetupTextureDestructionExpectations(
171    ::gfx::MockGLInterface* gl, GLenum target) {
172  GLuint texture_id = 0;
173  switch (target) {
174    case GL_TEXTURE_2D:
175      texture_id = kServiceDefaultTexture2dId;
176      break;
177    case GL_TEXTURE_CUBE_MAP:
178      texture_id = kServiceDefaultTextureCubemapId;
179      break;
180    case GL_TEXTURE_EXTERNAL_OES:
181      texture_id = kServiceDefaultExternalTextureId;
182      break;
183    case GL_TEXTURE_RECTANGLE_ARB:
184      texture_id = kServiceDefaultRectangleTextureId;
185      break;
186    default:
187      NOTREACHED();
188  }
189
190  EXPECT_CALL(*gl, DeleteTextures(1, Pointee(texture_id)))
191      .Times(1)
192      .RetiresOnSaturation();
193}
194
195void TestHelper::SetupTextureManagerDestructionExpectations(
196    ::gfx::MockGLInterface* gl,
197    const char* extensions) {
198  SetupTextureDestructionExpectations(gl, GL_TEXTURE_2D);
199  SetupTextureDestructionExpectations(gl, GL_TEXTURE_CUBE_MAP);
200
201  bool ext_image_external = false;
202  bool arb_texture_rectangle = false;
203  base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
204  while (t.GetNext()) {
205    if (t.token() == "GL_OES_EGL_image_external") {
206      ext_image_external = true;
207      break;
208    }
209    if (t.token() == "GL_ARB_texture_rectangle") {
210      arb_texture_rectangle = true;
211      break;
212    }
213  }
214
215  if (ext_image_external) {
216    SetupTextureDestructionExpectations(gl, GL_TEXTURE_EXTERNAL_OES);
217  }
218  if (arb_texture_rectangle) {
219    SetupTextureDestructionExpectations(gl, GL_TEXTURE_RECTANGLE_ARB);
220  }
221
222  EXPECT_CALL(*gl, DeleteTextures(4, _))
223      .Times(1)
224      .RetiresOnSaturation();
225}
226
227void TestHelper::SetupContextGroupInitExpectations(
228      ::gfx::MockGLInterface* gl,
229      const DisallowedFeatures& disallowed_features,
230      const char* extensions) {
231  InSequence sequence;
232
233  SetupFeatureInfoInitExpectations(gl, extensions);
234
235  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_RENDERBUFFER_SIZE, _))
236      .WillOnce(SetArgumentPointee<1>(kMaxRenderbufferSize))
237      .RetiresOnSaturation();
238  if (strstr(extensions, "GL_EXT_framebuffer_multisample")) {
239    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_SAMPLES, _))
240        .WillOnce(SetArgumentPointee<1>(kMaxSamples))
241        .RetiresOnSaturation();
242  }
243  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_ATTRIBS, _))
244      .WillOnce(SetArgumentPointee<1>(kNumVertexAttribs))
245      .RetiresOnSaturation();
246  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, _))
247      .WillOnce(SetArgumentPointee<1>(kNumTextureUnits))
248      .RetiresOnSaturation();
249  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_SIZE, _))
250      .WillOnce(SetArgumentPointee<1>(kMaxTextureSize))
251      .RetiresOnSaturation();
252  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, _))
253      .WillOnce(SetArgumentPointee<1>(kMaxCubeMapTextureSize))
254      .RetiresOnSaturation();
255  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, _))
256      .WillOnce(SetArgumentPointee<1>(kMaxTextureImageUnits))
257      .RetiresOnSaturation();
258  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, _))
259      .WillOnce(SetArgumentPointee<1>(kMaxVertexTextureImageUnits))
260      .RetiresOnSaturation();
261  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, _))
262      .WillOnce(SetArgumentPointee<1>(kMaxFragmentUniformComponents))
263      .RetiresOnSaturation();
264  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VARYING_FLOATS, _))
265      .WillOnce(SetArgumentPointee<1>(kMaxVaryingFloats))
266      .RetiresOnSaturation();
267  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, _))
268      .WillOnce(SetArgumentPointee<1>(kMaxVertexUniformComponents))
269      .RetiresOnSaturation();
270
271  SetupTextureManagerInitExpectations(gl, extensions);
272}
273
274void TestHelper::SetupFeatureInfoInitExpectations(
275      ::gfx::MockGLInterface* gl, const char* extensions) {
276  SetupFeatureInfoInitExpectationsWithGLVersion(gl, extensions, "");
277}
278
279void TestHelper::SetupFeatureInfoInitExpectationsWithGLVersion(
280     ::gfx::MockGLInterface* gl,
281     const char* extensions,
282     const char* version) {
283  InSequence sequence;
284
285  EXPECT_CALL(*gl, GetString(GL_EXTENSIONS))
286      .WillOnce(Return(reinterpret_cast<const uint8*>(extensions)))
287      .RetiresOnSaturation();
288  EXPECT_CALL(*gl, GetString(GL_VENDOR))
289      .WillOnce(Return(reinterpret_cast<const uint8*>("")))
290      .RetiresOnSaturation();
291  EXPECT_CALL(*gl, GetString(GL_RENDERER))
292      .WillOnce(Return(reinterpret_cast<const uint8*>("")))
293      .RetiresOnSaturation();
294  EXPECT_CALL(*gl, GetString(GL_VERSION))
295      .WillOnce(Return(reinterpret_cast<const uint8*>(version)))
296      .RetiresOnSaturation();
297}
298
299void TestHelper::SetupExpectationsForClearingUniforms(
300    ::gfx::MockGLInterface* gl, UniformInfo* uniforms, size_t num_uniforms) {
301  for (size_t ii = 0; ii < num_uniforms; ++ii) {
302    const UniformInfo& info = uniforms[ii];
303    switch (info.type) {
304    case GL_FLOAT:
305      EXPECT_CALL(*gl, Uniform1fv(info.real_location, info.size, _))
306          .Times(1)
307          .RetiresOnSaturation();
308      break;
309    case GL_FLOAT_VEC2:
310      EXPECT_CALL(*gl, Uniform2fv(info.real_location, info.size, _))
311          .Times(1)
312          .RetiresOnSaturation();
313      break;
314    case GL_FLOAT_VEC3:
315      EXPECT_CALL(*gl, Uniform3fv(info.real_location, info.size, _))
316          .Times(1)
317          .RetiresOnSaturation();
318      break;
319    case GL_FLOAT_VEC4:
320      EXPECT_CALL(*gl, Uniform4fv(info.real_location, info.size, _))
321          .Times(1)
322          .RetiresOnSaturation();
323      break;
324    case GL_INT:
325    case GL_BOOL:
326    case GL_SAMPLER_2D:
327    case GL_SAMPLER_CUBE:
328    case GL_SAMPLER_EXTERNAL_OES:
329    case GL_SAMPLER_3D_OES:
330    case GL_SAMPLER_2D_RECT_ARB:
331      EXPECT_CALL(*gl, Uniform1iv(info.real_location, info.size, _))
332          .Times(1)
333          .RetiresOnSaturation();
334      break;
335    case GL_INT_VEC2:
336    case GL_BOOL_VEC2:
337      EXPECT_CALL(*gl, Uniform2iv(info.real_location, info.size, _))
338          .Times(1)
339          .RetiresOnSaturation();
340      break;
341    case GL_INT_VEC3:
342    case GL_BOOL_VEC3:
343      EXPECT_CALL(*gl, Uniform3iv(info.real_location, info.size, _))
344          .Times(1)
345          .RetiresOnSaturation();
346      break;
347    case GL_INT_VEC4:
348    case GL_BOOL_VEC4:
349      EXPECT_CALL(*gl, Uniform4iv(info.real_location, info.size, _))
350          .Times(1)
351          .RetiresOnSaturation();
352      break;
353    case GL_FLOAT_MAT2:
354      EXPECT_CALL(*gl, UniformMatrix2fv(
355          info.real_location, info.size, false, _))
356          .Times(1)
357          .RetiresOnSaturation();
358      break;
359    case GL_FLOAT_MAT3:
360      EXPECT_CALL(*gl, UniformMatrix3fv(
361          info.real_location, info.size, false, _))
362          .Times(1)
363          .RetiresOnSaturation();
364      break;
365    case GL_FLOAT_MAT4:
366      EXPECT_CALL(*gl, UniformMatrix4fv(
367          info.real_location, info.size, false, _))
368          .Times(1)
369          .RetiresOnSaturation();
370      break;
371    default:
372      NOTREACHED();
373      break;
374    }
375  }
376}
377
378void TestHelper::SetupProgramSuccessExpectations(
379    ::gfx::MockGLInterface* gl,
380    AttribInfo* attribs, size_t num_attribs,
381    UniformInfo* uniforms, size_t num_uniforms,
382    GLuint service_id) {
383  EXPECT_CALL(*gl,
384      GetProgramiv(service_id, GL_LINK_STATUS, _))
385      .WillOnce(SetArgumentPointee<2>(1))
386      .RetiresOnSaturation();
387  EXPECT_CALL(*gl,
388      GetProgramiv(service_id, GL_INFO_LOG_LENGTH, _))
389      .WillOnce(SetArgumentPointee<2>(0))
390      .RetiresOnSaturation();
391  EXPECT_CALL(*gl,
392      GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTES, _))
393      .WillOnce(SetArgumentPointee<2>(num_attribs))
394      .RetiresOnSaturation();
395  size_t max_attrib_len = 0;
396  for (size_t ii = 0; ii < num_attribs; ++ii) {
397    size_t len = strlen(attribs[ii].name) + 1;
398    max_attrib_len = std::max(max_attrib_len, len);
399  }
400  EXPECT_CALL(*gl,
401      GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, _))
402      .WillOnce(SetArgumentPointee<2>(max_attrib_len))
403      .RetiresOnSaturation();
404
405  for (size_t ii = 0; ii < num_attribs; ++ii) {
406    const AttribInfo& info = attribs[ii];
407    EXPECT_CALL(*gl,
408        GetActiveAttrib(service_id, ii,
409                        max_attrib_len, _, _, _, _))
410        .WillOnce(DoAll(
411            SetArgumentPointee<3>(strlen(info.name)),
412            SetArgumentPointee<4>(info.size),
413            SetArgumentPointee<5>(info.type),
414            SetArrayArgument<6>(info.name,
415                                info.name + strlen(info.name) + 1)))
416        .RetiresOnSaturation();
417    if (!ProgramManager::IsInvalidPrefix(info.name, strlen(info.name))) {
418      EXPECT_CALL(*gl, GetAttribLocation(service_id, StrEq(info.name)))
419          .WillOnce(Return(info.location))
420          .RetiresOnSaturation();
421    }
422  }
423  EXPECT_CALL(*gl,
424      GetProgramiv(service_id, GL_ACTIVE_UNIFORMS, _))
425      .WillOnce(SetArgumentPointee<2>(num_uniforms))
426      .RetiresOnSaturation();
427
428  size_t max_uniform_len = 0;
429  for (size_t ii = 0; ii < num_uniforms; ++ii) {
430    size_t len = strlen(uniforms[ii].name) + 1;
431    max_uniform_len = std::max(max_uniform_len, len);
432  }
433  EXPECT_CALL(*gl,
434      GetProgramiv(service_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
435      .WillOnce(SetArgumentPointee<2>(max_uniform_len))
436      .RetiresOnSaturation();
437  for (size_t ii = 0; ii < num_uniforms; ++ii) {
438    const UniformInfo& info = uniforms[ii];
439    EXPECT_CALL(*gl,
440        GetActiveUniform(service_id, ii,
441                         max_uniform_len, _, _, _, _))
442        .WillOnce(DoAll(
443            SetArgumentPointee<3>(strlen(info.name)),
444            SetArgumentPointee<4>(info.size),
445            SetArgumentPointee<5>(info.type),
446            SetArrayArgument<6>(info.name,
447                                info.name + strlen(info.name) + 1)))
448        .RetiresOnSaturation();
449  }
450
451  for (int pass = 0; pass < 2; ++pass) {
452    for (size_t ii = 0; ii < num_uniforms; ++ii) {
453      const UniformInfo& info = uniforms[ii];
454      if (ProgramManager::IsInvalidPrefix(info.name, strlen(info.name))) {
455        continue;
456      }
457      if (pass == 0) {
458        EXPECT_CALL(*gl, GetUniformLocation(service_id, StrEq(info.name)))
459            .WillOnce(Return(info.real_location))
460            .RetiresOnSaturation();
461      }
462      if ((pass == 0 && info.desired_location >= 0) ||
463          (pass == 1 && info.desired_location < 0)) {
464        if (info.size > 1) {
465          std::string base_name = info.name;
466          size_t array_pos = base_name.rfind("[0]");
467          if (base_name.size() > 3 && array_pos == base_name.size() - 3) {
468            base_name = base_name.substr(0, base_name.size() - 3);
469          }
470          for (GLsizei jj = 1; jj < info.size; ++jj) {
471            std::string element_name(
472                std::string(base_name) + "[" + base::IntToString(jj) + "]");
473            EXPECT_CALL(*gl, GetUniformLocation(
474                service_id, StrEq(element_name)))
475                .WillOnce(Return(info.real_location + jj * 2))
476                .RetiresOnSaturation();
477          }
478        }
479      }
480    }
481  }
482}
483
484void TestHelper::SetupShader(
485    ::gfx::MockGLInterface* gl,
486    AttribInfo* attribs, size_t num_attribs,
487    UniformInfo* uniforms, size_t num_uniforms,
488    GLuint service_id) {
489  InSequence s;
490
491  EXPECT_CALL(*gl,
492      LinkProgram(service_id))
493      .Times(1)
494      .RetiresOnSaturation();
495
496  SetupProgramSuccessExpectations(
497      gl, attribs, num_attribs, uniforms, num_uniforms, service_id);
498}
499
500void TestHelper::DoBufferData(
501    ::gfx::MockGLInterface* gl, MockErrorState* error_state,
502    BufferManager* manager, Buffer* buffer, GLsizeiptr size, GLenum usage,
503    const GLvoid* data, GLenum error) {
504  EXPECT_CALL(*error_state, CopyRealGLErrorsToWrapper(_, _, _))
505      .Times(1)
506      .RetiresOnSaturation();
507  if (manager->IsUsageClientSideArray(usage)) {
508    EXPECT_CALL(*gl, BufferData(
509        buffer->target(), 0, _, usage))
510        .Times(1)
511        .RetiresOnSaturation();
512  } else {
513    EXPECT_CALL(*gl, BufferData(
514        buffer->target(), size, _, usage))
515        .Times(1)
516        .RetiresOnSaturation();
517  }
518  EXPECT_CALL(*error_state, PeekGLError(_, _, _))
519      .WillOnce(Return(error))
520      .RetiresOnSaturation();
521  manager->DoBufferData(error_state, buffer, size, usage, data);
522}
523
524void TestHelper::SetTexParameterWithExpectations(
525    ::gfx::MockGLInterface* gl, MockErrorState* error_state,
526    TextureManager* manager, Texture* texture,
527    GLenum pname, GLint value, GLenum error) {
528  if (error == GL_NO_ERROR) {
529    if (pname != GL_TEXTURE_POOL_CHROMIUM) {
530      EXPECT_CALL(*gl, TexParameteri(texture->target(), pname, value))
531          .Times(1)
532          .RetiresOnSaturation();
533    }
534  } else if (error == GL_INVALID_ENUM) {
535    EXPECT_CALL(*error_state, SetGLErrorInvalidEnum(_, _, _, value, _))
536        .Times(1)
537        .RetiresOnSaturation();
538  } else {
539    EXPECT_CALL(*error_state, SetGLErrorInvalidParam(_, _, error, _, _, _))
540        .Times(1)
541        .RetiresOnSaturation();
542  }
543  manager->SetParameter("", error_state, texture, pname, value);
544}
545
546ScopedGLImplementationSetter::ScopedGLImplementationSetter(
547    gfx::GLImplementation implementation)
548    : old_implementation_(gfx::GetGLImplementation()) {
549  gfx::SetGLImplementation(implementation);
550}
551
552ScopedGLImplementationSetter::~ScopedGLImplementationSetter() {
553  gfx::SetGLImplementation(old_implementation_);
554}
555
556}  // namespace gles2
557}  // namespace gpu
558
559