blob: 5228a0fd125a5eb78dcf21bfa00a9b3293d55708 [file] [log] [blame]
jvanverth9f372462016-04-06 06:08:59 -07001/*
2* Copyright 2016 Google Inc.
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
jvanverth34524262016-05-04 13:49:13 -07008#include "Viewer.h"
jvanverth9f372462016-04-06 06:08:59 -07009
jvanverth2bb3b6d2016-04-08 07:24:09 -070010#include "GMSlide.h"
11#include "SKPSlide.h"
jvanverth9f372462016-04-06 06:08:59 -070012
jvanverth2bb3b6d2016-04-08 07:24:09 -070013#include "SkCanvas.h"
14#include "SkCommonFlags.h"
15#include "SkOSFile.h"
16#include "SkRandom.h"
17#include "SkStream.h"
jvanverth9f372462016-04-06 06:08:59 -070018
jvanverth34524262016-05-04 13:49:13 -070019using namespace sk_app;
20
jvanverth9f372462016-04-06 06:08:59 -070021Application* Application::Create(int argc, char** argv, void* platformData) {
jvanverth34524262016-05-04 13:49:13 -070022 return new Viewer(argc, argv, platformData);
jvanverth9f372462016-04-06 06:08:59 -070023}
24
jvanverth9f372462016-04-06 06:08:59 -070025static void on_paint_handler(SkCanvas* canvas, void* userData) {
jvanverth34524262016-05-04 13:49:13 -070026 Viewer* vv = reinterpret_cast<Viewer*>(userData);
jvanverth9f372462016-04-06 06:08:59 -070027
28 return vv->onPaint(canvas);
29}
30
liyuqiand3cdbca2016-05-17 12:44:20 -070031static bool on_touch_handler(int owner, Window::InputState state, float x, float y, void* userData)
32{
33 Viewer* viewer = reinterpret_cast<Viewer*>(userData);
34
35 return viewer->onTouch(owner, state, x, y);
36}
37
liyuqiane5a6cd92016-05-27 08:52:52 -070038static void on_ui_state_changed_handler(const SkString& stateName, const SkString& stateValue, void* userData) {
39 Viewer* viewer = reinterpret_cast<Viewer*>(userData);
40
41 return viewer->onUIStateChanged(stateName, stateValue);
42}
43
44DEFINE_bool2(fullscreen, f, true, "Run fullscreen.");
jvanverth2bb3b6d2016-04-08 07:24:09 -070045DEFINE_string(key, "", "Space-separated key/value pairs to add to JSON identifying this builder.");
46DEFINE_string2(match, m, nullptr,
47 "[~][^]substring[$] [...] of bench name to run.\n"
48 "Multiple matches may be separated by spaces.\n"
49 "~ causes a matching bench to always be skipped\n"
50 "^ requires the start of the bench to match\n"
51 "$ requires the end of the bench to match\n"
52 "^ and $ requires an exact match\n"
53 "If a bench does not match any list entry,\n"
54 "it is skipped unless some list entry starts with ~");
55DEFINE_string(skps, "skps", "Directory to read skps from.");
jvanverth85f758c2016-05-27 06:47:08 -070056DEFINE_bool(vulkan, true, "Run with Vulkan.");
jvanverth2bb3b6d2016-04-08 07:24:09 -070057
jvanverthaf236b52016-05-20 06:01:06 -070058const char *kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
59 " [OpenGL]",
60 " [Vulkan]"
61};
62
liyuqiane5a6cd92016-05-27 08:52:52 -070063const char* kName = "name";
64const char* kValue = "value";
65const char* kOptions = "options";
66const char* kSlideStateName = "Slide";
67const char* kBackendStateName = "Backend";
68
jvanverth34524262016-05-04 13:49:13 -070069Viewer::Viewer(int argc, char** argv, void* platformData)
jvanverthc265a922016-04-08 12:51:45 -070070 : fCurrentMeasurement(0)
71 , fDisplayStats(false)
jvanverthaf236b52016-05-20 06:01:06 -070072 , fBackendType(sk_app::Window::kVulkan_BackendType)
egdaniel2a0bb0a2016-04-11 08:30:40 -070073 , fZoomCenterX(0.0f)
74 , fZoomCenterY(0.0f)
75 , fZoomLevel(0.0f)
76 , fZoomScale(SK_Scalar1)
jvanverthc265a922016-04-08 12:51:45 -070077{
jvanverth3d6ed3a2016-04-07 11:09:51 -070078 memset(fMeasurements, 0, sizeof(fMeasurements));
jvanverth9f372462016-04-06 06:08:59 -070079
jvanverth2bb3b6d2016-04-08 07:24:09 -070080 SkDebugf("Command line arguments: ");
81 for (int i = 1; i < argc; ++i) {
82 SkDebugf("%s ", argv[i]);
83 }
84 SkDebugf("\n");
85
86 SkCommandLineFlags::Parse(argc, argv);
87
jvanverth85f758c2016-05-27 06:47:08 -070088 fBackendType = FLAGS_vulkan ? sk_app::Window::kVulkan_BackendType
89 : sk_app::Window::kNativeGL_BackendType;
90
jvanverth9f372462016-04-06 06:08:59 -070091 fWindow = Window::CreateNativeWindow(platformData);
jvanverthaf236b52016-05-20 06:01:06 -070092 fWindow->attach(fBackendType, DisplayParams());
jvanverth9f372462016-04-06 06:08:59 -070093
94 // register callbacks
brianosman622c8d52016-05-10 06:50:49 -070095 fCommands.attach(fWindow);
jvanverth9f372462016-04-06 06:08:59 -070096 fWindow->registerPaintFunc(on_paint_handler, this);
liyuqiand3cdbca2016-05-17 12:44:20 -070097 fWindow->registerTouchFunc(on_touch_handler, this);
liyuqiane5a6cd92016-05-27 08:52:52 -070098 fWindow->registerUIStateChangedFunc(on_ui_state_changed_handler, this);
jvanverth9f372462016-04-06 06:08:59 -070099
brianosman622c8d52016-05-10 06:50:49 -0700100 // add key-bindings
101 fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
102 this->fDisplayStats = !this->fDisplayStats;
103 fWindow->inval();
104 });
105 fCommands.addCommand('c', "Modes", "Toggle sRGB color mode", [this]() {
106 DisplayParams params = fWindow->getDisplayParams();
107 params.fProfileType = (kLinear_SkColorProfileType == params.fProfileType)
108 ? kSRGB_SkColorProfileType : kLinear_SkColorProfileType;
109 fWindow->setDisplayParams(params);
110 this->updateTitle();
111 fWindow->inval();
112 });
113 fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
114 int previousSlide = fCurrentSlide;
115 fCurrentSlide++;
116 if (fCurrentSlide >= fSlides.count()) {
117 fCurrentSlide = 0;
118 }
119 this->setupCurrentSlide(previousSlide);
120 });
121 fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
122 int previousSlide = fCurrentSlide;
123 fCurrentSlide--;
124 if (fCurrentSlide < 0) {
125 fCurrentSlide = fSlides.count() - 1;
126 }
127 this->setupCurrentSlide(previousSlide);
128 });
129 fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
130 this->changeZoomLevel(1.f / 32.f);
131 fWindow->inval();
132 });
133 fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
134 this->changeZoomLevel(-1.f / 32.f);
135 fWindow->inval();
136 });
jvanverth85f758c2016-05-27 06:47:08 -0700137#if 0 // this doesn't seem to work on any platform right now
jvanverthaf236b52016-05-20 06:01:06 -0700138#ifndef SK_BUILD_FOR_ANDROID
139 fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
140 fWindow->detach();
141
142 if (sk_app::Window::kVulkan_BackendType == fBackendType) {
143 fBackendType = sk_app::Window::kNativeGL_BackendType;
144 }
jvanverth85f758c2016-05-27 06:47:08 -0700145 // TODO: get Vulkan -> OpenGL working on Windows without swapchain creation failure
jvanverthaf236b52016-05-20 06:01:06 -0700146 //else if (sk_app::Window::kNativeGL_BackendType == fBackendType) {
147 // fBackendType = sk_app::Window::kVulkan_BackendType;
148 //}
149
150 fWindow->attach(fBackendType, DisplayParams());
151 this->updateTitle();
jvanverth85f758c2016-05-27 06:47:08 -0700152 fWindow->inval();
jvanverthaf236b52016-05-20 06:01:06 -0700153 });
154#endif
jvanverth85f758c2016-05-27 06:47:08 -0700155#endif
brianosman622c8d52016-05-10 06:50:49 -0700156
jvanverth2bb3b6d2016-04-08 07:24:09 -0700157 // set up slides
158 this->initSlides();
159
djsollen12d62a72016-04-21 07:59:44 -0700160 fAnimTimer.run();
161
jvanverth2bb3b6d2016-04-08 07:24:09 -0700162 // set up first frame
jvanverth2bb3b6d2016-04-08 07:24:09 -0700163 fCurrentSlide = 0;
jvanverthc265a922016-04-08 12:51:45 -0700164 setupCurrentSlide(-1);
jvanverthc265a922016-04-08 12:51:45 -0700165
jvanverth9f372462016-04-06 06:08:59 -0700166 fWindow->show();
167}
168
jvanverth34524262016-05-04 13:49:13 -0700169void Viewer::initSlides() {
jvanverth2bb3b6d2016-04-08 07:24:09 -0700170 const skiagm::GMRegistry* gms(skiagm::GMRegistry::Head());
171 while (gms) {
172 SkAutoTDelete<skiagm::GM> gm(gms->factory()(nullptr));
173
174 if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
175 sk_sp<Slide> slide(new GMSlide(gm.release()));
176 fSlides.push_back(slide);
177 }
178
179 gms = gms->next();
180 }
181
182 // reverse array
183 for (int i = 0; i < fSlides.count()/2; ++i) {
184 sk_sp<Slide> temp = fSlides[i];
185 fSlides[i] = fSlides[fSlides.count() - i - 1];
186 fSlides[fSlides.count() - i - 1] = temp;
187 }
188
189 // SKPs
190 for (int i = 0; i < FLAGS_skps.count(); i++) {
191 if (SkStrEndsWith(FLAGS_skps[i], ".skp")) {
jvanverthc265a922016-04-08 12:51:45 -0700192 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) {
193 continue;
194 }
195
jvanverth2bb3b6d2016-04-08 07:24:09 -0700196 SkString path(FLAGS_skps[i]);
jvanverthc265a922016-04-08 12:51:45 -0700197 sk_sp<SKPSlide> slide(new SKPSlide(SkOSPath::Basename(path.c_str()), path));
jvanverth2bb3b6d2016-04-08 07:24:09 -0700198 if (slide) {
199 fSlides.push_back(slide);
200 }
201 } else {
202 SkOSFile::Iter it(FLAGS_skps[i], ".skp");
jvanverthc265a922016-04-08 12:51:45 -0700203 SkString skpName;
204 while (it.next(&skpName)) {
205 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, skpName.c_str())) {
206 continue;
207 }
208
209 SkString path = SkOSPath::Join(FLAGS_skps[i], skpName.c_str());
210 sk_sp<SKPSlide> slide(new SKPSlide(skpName, path));
jvanverth2bb3b6d2016-04-08 07:24:09 -0700211 if (slide) {
212 fSlides.push_back(slide);
213 }
214 }
215 }
216 }
217}
218
219
jvanverth34524262016-05-04 13:49:13 -0700220Viewer::~Viewer() {
jvanverth9f372462016-04-06 06:08:59 -0700221 fWindow->detach();
222 delete fWindow;
223}
224
brianosman05de2162016-05-06 13:28:57 -0700225void Viewer::updateTitle() {
jvanverth34524262016-05-04 13:49:13 -0700226 SkString title("Viewer: ");
jvanverthc265a922016-04-08 12:51:45 -0700227 title.append(fSlides[fCurrentSlide]->getName());
brianosman05de2162016-05-06 13:28:57 -0700228 if (kSRGB_SkColorProfileType == fWindow->getDisplayParams().fProfileType) {
229 title.append(" sRGB");
230 }
jvanverthaf236b52016-05-20 06:01:06 -0700231 title.append(kBackendTypeStrings[fBackendType]);
brianosman05de2162016-05-06 13:28:57 -0700232 fWindow->setTitle(title.c_str());
233}
234
235void Viewer::setupCurrentSlide(int previousSlide) {
liyuqiane5a6cd92016-05-27 08:52:52 -0700236 if (fCurrentSlide == previousSlide) {
237 return; // no change; do nothing
238 }
239
liyuqiane46e4f02016-05-20 07:32:19 -0700240 fGesture.reset();
241 fDefaultMatrix.reset();
242 fDefaultMatrixInv.reset();
243
244 if (fWindow->supportsContentRect() && fWindow->scaleContentToFit()) {
245 const SkRect contentRect = fWindow->getContentRect();
246 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
247 const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
248 if (contentRect.width() > 0 && contentRect.height() > 0) {
249 fDefaultMatrix.setRectToRect(slideBounds, contentRect, SkMatrix::kStart_ScaleToFit);
liyuqianbeb1c672016-05-20 11:41:01 -0700250 SkAssertResult(fDefaultMatrix.invert(&fDefaultMatrixInv));
liyuqiane46e4f02016-05-20 07:32:19 -0700251 }
252 }
253
254 if (fWindow->supportsContentRect()) {
255 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
256 SkRect windowRect = fWindow->getContentRect();
257 fDefaultMatrixInv.mapRect(&windowRect);
jvanverth1e305ba2016-06-01 09:39:15 -0700258 fGesture.setTransLimit(SkRect::MakeWH(SkIntToScalar(slideSize.width()),
259 SkIntToScalar(slideSize.height())),
260 windowRect);
liyuqiane46e4f02016-05-20 07:32:19 -0700261 }
262
brianosman05de2162016-05-06 13:28:57 -0700263 this->updateTitle();
liyuqiane5a6cd92016-05-27 08:52:52 -0700264 this->updateUIState();
jvanverthc265a922016-04-08 12:51:45 -0700265 fSlides[fCurrentSlide]->load();
266 if (previousSlide >= 0) {
267 fSlides[previousSlide]->unload();
268 }
jvanverthc265a922016-04-08 12:51:45 -0700269 fWindow->inval();
270}
271
272#define MAX_ZOOM_LEVEL 8
273#define MIN_ZOOM_LEVEL -8
274
jvanverth34524262016-05-04 13:49:13 -0700275void Viewer::changeZoomLevel(float delta) {
jvanverthc265a922016-04-08 12:51:45 -0700276 fZoomLevel += delta;
277 if (fZoomLevel > 0) {
278 fZoomLevel = SkMinScalar(fZoomLevel, MAX_ZOOM_LEVEL);
279 fZoomScale = fZoomLevel + SK_Scalar1;
280 } else if (fZoomLevel < 0) {
281 fZoomLevel = SkMaxScalar(fZoomLevel, MIN_ZOOM_LEVEL);
282 fZoomScale = SK_Scalar1 / (SK_Scalar1 - fZoomLevel);
283 } else {
284 fZoomScale = SK_Scalar1;
285 }
jvanverthc265a922016-04-08 12:51:45 -0700286}
287
liyuqiand3cdbca2016-05-17 12:44:20 -0700288SkMatrix Viewer::computeMatrix() {
jvanverthc265a922016-04-08 12:51:45 -0700289 SkMatrix m;
290 m.reset();
291
292 if (fZoomLevel) {
293 SkPoint center;
294 //m = this->getLocalMatrix();//.invert(&m);
295 m.mapXY(fZoomCenterX, fZoomCenterY, &center);
296 SkScalar cx = center.fX;
297 SkScalar cy = center.fY;
298
299 m.setTranslate(-cx, -cy);
300 m.postScale(fZoomScale, fZoomScale);
301 m.postTranslate(cx, cy);
302 }
303
liyuqiand3cdbca2016-05-17 12:44:20 -0700304 m.preConcat(fGesture.localM());
305 m.preConcat(fGesture.globalM());
jvanverthc265a922016-04-08 12:51:45 -0700306
liyuqiand3cdbca2016-05-17 12:44:20 -0700307 return m;
jvanverthc265a922016-04-08 12:51:45 -0700308}
309
jvanverth34524262016-05-04 13:49:13 -0700310void Viewer::onPaint(SkCanvas* canvas) {
jvanverthc265a922016-04-08 12:51:45 -0700311 int count = canvas->save();
djsollen12d62a72016-04-21 07:59:44 -0700312
313 if (fWindow->supportsContentRect()) {
314 SkRect contentRect = fWindow->getContentRect();
315 canvas->clipRect(contentRect);
316 canvas->translate(contentRect.fLeft, contentRect.fTop);
317 }
318
319 canvas->clear(SK_ColorWHITE);
liyuqiane46e4f02016-05-20 07:32:19 -0700320 canvas->concat(fDefaultMatrix);
liyuqiand3cdbca2016-05-17 12:44:20 -0700321 canvas->concat(computeMatrix());
jvanverth3d6ed3a2016-04-07 11:09:51 -0700322
jvanverthc265a922016-04-08 12:51:45 -0700323 fSlides[fCurrentSlide]->draw(canvas);
324 canvas->restoreToCount(count);
325
326 if (fDisplayStats) {
327 drawStats(canvas);
328 }
brianosman622c8d52016-05-10 06:50:49 -0700329 fCommands.drawHelp(canvas);
jvanverth3d6ed3a2016-04-07 11:09:51 -0700330}
331
liyuqiand3cdbca2016-05-17 12:44:20 -0700332bool Viewer::onTouch(int owner, Window::InputState state, float x, float y) {
333 void* castedOwner = reinterpret_cast<void*>(owner);
liyuqiane46e4f02016-05-20 07:32:19 -0700334 SkPoint touchPoint = fDefaultMatrixInv.mapXY(x, y);
liyuqiand3cdbca2016-05-17 12:44:20 -0700335 switch (state) {
336 case Window::kUp_InputState: {
337 fGesture.touchEnd(castedOwner);
338 break;
339 }
340 case Window::kDown_InputState: {
liyuqiane46e4f02016-05-20 07:32:19 -0700341 fGesture.touchBegin(castedOwner, touchPoint.fX, touchPoint.fY);
liyuqiand3cdbca2016-05-17 12:44:20 -0700342 break;
343 }
344 case Window::kMove_InputState: {
liyuqiane46e4f02016-05-20 07:32:19 -0700345 fGesture.touchMoved(castedOwner, touchPoint.fX, touchPoint.fY);
liyuqiand3cdbca2016-05-17 12:44:20 -0700346 break;
347 }
348 }
349 fWindow->inval();
350 return true;
351}
352
jvanverth34524262016-05-04 13:49:13 -0700353void Viewer::drawStats(SkCanvas* canvas) {
jvanverth3d6ed3a2016-04-07 11:09:51 -0700354 static const float kPixelPerMS = 2.0f;
355 static const int kDisplayWidth = 130;
356 static const int kDisplayHeight = 100;
357 static const int kDisplayPadding = 10;
358 static const int kGraphPadding = 3;
359 static const SkScalar kBaseMS = 1000.f / 60.f; // ms/frame to hit 60 fps
360
361 SkISize canvasSize = canvas->getDeviceSize();
362 SkRect rect = SkRect::MakeXYWH(SkIntToScalar(canvasSize.fWidth-kDisplayWidth-kDisplayPadding),
363 SkIntToScalar(kDisplayPadding),
364 SkIntToScalar(kDisplayWidth), SkIntToScalar(kDisplayHeight));
365 SkPaint paint;
366 canvas->save();
367
djsollen12d62a72016-04-21 07:59:44 -0700368 if (fWindow->supportsContentRect()) {
369 SkRect contentRect = fWindow->getContentRect();
370 canvas->clipRect(contentRect);
371 canvas->translate(contentRect.fLeft, contentRect.fTop);
372 }
373
jvanverth3d6ed3a2016-04-07 11:09:51 -0700374 canvas->clipRect(rect);
375 paint.setColor(SK_ColorBLACK);
376 canvas->drawRect(rect, paint);
377 // draw the 16ms line
378 paint.setColor(SK_ColorLTGRAY);
379 canvas->drawLine(rect.fLeft, rect.fBottom - kBaseMS*kPixelPerMS,
380 rect.fRight, rect.fBottom - kBaseMS*kPixelPerMS, paint);
381 paint.setColor(SK_ColorRED);
382 paint.setStyle(SkPaint::kStroke_Style);
383 canvas->drawRect(rect, paint);
384
385 int x = SkScalarTruncToInt(rect.fLeft) + kGraphPadding;
386 const int xStep = 2;
387 const int startY = SkScalarTruncToInt(rect.fBottom);
388 int i = fCurrentMeasurement;
389 do {
390 int endY = startY - (int)(fMeasurements[i] * kPixelPerMS + 0.5); // round to nearest value
391 canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY),
392 SkIntToScalar(x), SkIntToScalar(endY), paint);
393 i++;
394 i &= (kMeasurementCount - 1); // fast mod
395 x += xStep;
396 } while (i != fCurrentMeasurement);
jvanverth9f372462016-04-06 06:08:59 -0700397
398 canvas->restore();
399}
400
jvanverth34524262016-05-04 13:49:13 -0700401void Viewer::onIdle(double ms) {
jvanverth3d6ed3a2016-04-07 11:09:51 -0700402 // Record measurements
403 fMeasurements[fCurrentMeasurement++] = ms;
404 fCurrentMeasurement &= (kMeasurementCount - 1); // fast mod
405 SkASSERT(fCurrentMeasurement < kMeasurementCount);
406
jvanverthc265a922016-04-08 12:51:45 -0700407 fAnimTimer.updateTime();
jvanverth9d5e47f2016-04-26 08:01:33 -0700408 if (fSlides[fCurrentSlide]->animate(fAnimTimer) || fDisplayStats) {
jvanverthc265a922016-04-08 12:51:45 -0700409 fWindow->inval();
410 }
jvanverth9f372462016-04-06 06:08:59 -0700411}
liyuqiane5a6cd92016-05-27 08:52:52 -0700412
413void Viewer::updateUIState() {
414 Json::Value slideState(Json::objectValue);
415 slideState[kName] = kSlideStateName;
416 slideState[kValue] = fSlides[fCurrentSlide]->getName().c_str();
417 Json::Value allSlideNames(Json::arrayValue);
418 for(auto slide : fSlides) {
419 allSlideNames.append(Json::Value(slide->getName().c_str()));
420 }
421 slideState[kOptions] = allSlideNames;
422
423 // This state is currently a demo for the one without options.
424 // We will be able to change the backend too.
425 Json::Value backendState(Json::objectValue);
426 backendState[kName] = kBackendStateName;
427 backendState[kValue] = fBackendType == sk_app::Window::kVulkan_BackendType ?
428 "Vulkan" : "Other than Vulkan";
429 backendState[kOptions] = Json::Value(Json::arrayValue);
430
431 Json::Value state(Json::arrayValue);
432 state.append(slideState);
433 state.append(backendState);
434
435 fWindow->setUIState(state);
436}
437
438void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
439 // Currently, we only recognize the Slide state
440 if (stateName.equals(kSlideStateName)) {
441 int previousSlide = fCurrentSlide;
442 fCurrentSlide = 0;
443 for(auto slide : fSlides) {
444 if (slide->getName().equals(stateValue)) {
445 setupCurrentSlide(previousSlide);
446 break;
447 }
448 fCurrentSlide++;
449 }
450 if (fCurrentSlide >= fSlides.count()) {
451 fCurrentSlide = previousSlide;
452 SkDebugf("Slide not found: %s", stateValue.c_str());
453 }
454 } else {
455 SkDebugf("Unknown stateName: %s", stateName.c_str());
456 }
457}