1 /* 2 * Copyright 2012 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 8 #ifndef PictureRenderer_DEFINED 9 #define PictureRenderer_DEFINED 10 11 #include "SkCanvas.h" 12 #include "SkDrawFilter.h" 13 #include "SkJSONCPP.h" 14 #include "SkMath.h" 15 #include "SkPaint.h" 16 #include "SkPicture.h" 17 #include "SkPictureRecorder.h" 18 #include "SkRect.h" 19 #include "SkRefCnt.h" 20 #include "SkString.h" 21 #include "SkTDArray.h" 22 #include "SkTypes.h" 23 24 #if SK_SUPPORT_GPU 25 #include "GrContextFactory.h" 26 #include "GrContext.h" 27 #endif 28 29 #include "image_expectations.h" 30 31 class SkBitmap; 32 class SkCanvas; 33 class SkGLContext; 34 class SkThread; 35 36 namespace sk_tools { 37 38 class TiledPictureRenderer; 39 40 class PictureRenderer : public SkRefCnt { 41 42 public: 43 enum SkDeviceTypes { 44 #if SK_ANGLE 45 kAngle_DeviceType, 46 #endif 47 #if SK_MESA 48 kMesa_DeviceType, 49 #endif 50 kBitmap_DeviceType, 51 #if SK_SUPPORT_GPU 52 kGPU_DeviceType, 53 kNVPR_DeviceType, 54 #endif 55 }; 56 57 enum BBoxHierarchyType { 58 kNone_BBoxHierarchyType = 0, 59 kRTree_BBoxHierarchyType, 60 61 kLast_BBoxHierarchyType = kRTree_BBoxHierarchyType, 62 }; 63 64 // this uses SkPaint::Flags as a base and adds additional flags 65 enum DrawFilterFlags { 66 kNone_DrawFilterFlag = 0, 67 kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting 68 kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting 69 kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip 70 kMaskFilter_DrawFilterFlag = 0x80000, // toggles on/off mask filters (e.g., blurs) 71 }; 72 73 SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater); 74 SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags), 75 hinting_flag_must_be_greater); 76 SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags), 77 slight_hinting_flag_must_be_greater); 78 79 /** 80 * Called with each new SkPicture to render. 81 * 82 * @param pict The SkPicture to render. 83 * @param writePath The output directory within which this renderer should write all images, 84 * or NULL if this renderer should not write all images. 85 * @param mismatchPath The output directory within which this renderer should write any images 86 * which do not match expectations, or NULL if this renderer should not write mismatches. 87 * @param inputFilename The name of the input file we are rendering. 88 * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing 89 * bitmap images to disk. 90 * @param useMultiPictureDraw true if MultiPictureDraw should be used for rendering 91 */ 92 virtual void init(const SkPicture* pict, 93 const SkString* writePath, 94 const SkString* mismatchPath, 95 const SkString* inputFilename, 96 bool useChecksumBasedFilenames, 97 bool useMultiPictureDraw); 98 99 /** 100 * TODO(epoger): Temporary hack, while we work on http://skbug.com/2584 ('bench_pictures is 101 * timing reading pixels and writing json files'), such that: 102 * - render_pictures can call this method and continue to work 103 * - any other callers (bench_pictures) will skip calls to write() by default 104 */ enableWrites()105 void enableWrites() { fEnableWrites = true; } 106 107 /** 108 * Set the viewport so that only the portion listed gets drawn. 109 */ setViewport(SkISize size)110 void setViewport(SkISize size) { fViewport = size; } 111 112 /** 113 * Set the scale factor at which draw the picture. 114 */ setScaleFactor(SkScalar scale)115 void setScaleFactor(SkScalar scale) { fScaleFactor = scale; } 116 117 /** 118 * Perform any setup that should done prior to each iteration of render() which should not be 119 * timed. 120 */ setup()121 virtual void setup() {} 122 123 /** 124 * Perform the work. If this is being called within the context of bench_pictures, 125 * this is the step that will be timed. 126 * 127 * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses 128 * it is recording the source SkPicture into another SkPicture. 129 * 130 * If fWritePath has been specified, the result of the work will be written to that dir. 131 * If fMismatchPath has been specified, and the actual image result differs from its 132 * expectation, the result of the work will be written to that dir. 133 * 134 * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the 135 * output image into it, and return it here. (Some subclasses ignore this parameter) 136 * @return bool True if rendering succeeded and, if fWritePath had been specified, the output 137 * was successfully written to a file. 138 */ 139 virtual bool render(SkBitmap** out = NULL) = 0; 140 141 /** 142 * Called once finished with a particular SkPicture, before calling init again, and before 143 * being done with this Renderer. 144 */ 145 virtual void end(); 146 147 /** 148 * If this PictureRenderer is actually a TiledPictureRender, return a pointer to this as a 149 * TiledPictureRender so its methods can be called. 150 */ getTiledRenderer()151 virtual TiledPictureRenderer* getTiledRenderer() { return NULL; } 152 153 /** 154 * Resets the GPU's state. Does nothing if the backing is raster. For a GPU renderer, calls 155 * flush, swapBuffers and, if callFinish is true, finish. 156 * @param callFinish Whether to call finish. 157 */ 158 void resetState(bool callFinish); 159 160 /** 161 * Remove all decoded textures from the CPU caches and all uploaded textures 162 * from the GPU. 163 */ 164 void purgeTextures(); 165 166 /** 167 * Set the backend type. Returns true on success and false on failure. 168 */ 169 #if SK_SUPPORT_GPU 170 bool setDeviceType(SkDeviceTypes deviceType, GrGLStandard gpuAPI = kNone_GrGLStandard) { 171 #else 172 bool setDeviceType(SkDeviceTypes deviceType) { 173 #endif 174 fDeviceType = deviceType; 175 #if SK_SUPPORT_GPU 176 // In case this function is called more than once 177 SkSafeUnref(fGrContext); 178 fGrContext = NULL; 179 // Set to Native so it will have an initial value. 180 GrContextFactory::GLContextType glContextType = GrContextFactory::kNative_GLContextType; 181 #endif 182 switch(deviceType) { 183 case kBitmap_DeviceType: 184 return true; 185 #if SK_SUPPORT_GPU 186 case kGPU_DeviceType: 187 // Already set to GrContextFactory::kNative_GLContextType, above. 188 break; 189 case kNVPR_DeviceType: 190 glContextType = GrContextFactory::kNVPR_GLContextType; 191 break; 192 #if SK_ANGLE 193 case kAngle_DeviceType: 194 glContextType = GrContextFactory::kANGLE_GLContextType; 195 break; 196 #endif 197 #if SK_MESA 198 case kMesa_DeviceType: 199 glContextType = GrContextFactory::kMESA_GLContextType; 200 break; 201 #endif 202 #endif 203 default: 204 // Invalid device type. 205 return false; 206 } 207 #if SK_SUPPORT_GPU 208 fGrContext = fGrContextFactory.get(glContextType, gpuAPI); 209 if (NULL == fGrContext) { 210 return false; 211 } else { 212 fGrContext->ref(); 213 return true; 214 } 215 #endif 216 } 217 218 #if SK_SUPPORT_GPU setSampleCount(int sampleCount)219 void setSampleCount(int sampleCount) { 220 fSampleCount = sampleCount; 221 } 222 setUseDFText(bool useDFText)223 void setUseDFText(bool useDFText) { 224 fUseDFText = useDFText; 225 } 226 #endif 227 setDrawFilters(DrawFilterFlags const * const filters,const SkString & configName)228 void setDrawFilters(DrawFilterFlags const * const filters, const SkString& configName) { 229 memcpy(fDrawFilters, filters, sizeof(fDrawFilters)); 230 fDrawFiltersConfig = configName; 231 } 232 setBBoxHierarchyType(BBoxHierarchyType bbhType)233 void setBBoxHierarchyType(BBoxHierarchyType bbhType) { 234 fBBoxHierarchyType = bbhType; 235 } 236 getBBoxHierarchyType()237 BBoxHierarchyType getBBoxHierarchyType() { return fBBoxHierarchyType; } 238 setJsonSummaryPtr(ImageResultsAndExpectations * jsonSummaryPtr)239 void setJsonSummaryPtr(ImageResultsAndExpectations* jsonSummaryPtr) { 240 fJsonSummaryPtr = jsonSummaryPtr; 241 } 242 isUsingBitmapDevice()243 bool isUsingBitmapDevice() { 244 return kBitmap_DeviceType == fDeviceType; 245 } 246 getPerIterTimeFormat()247 virtual SkString getPerIterTimeFormat() { return SkString("%.2f"); } 248 getNormalTimeFormat()249 virtual SkString getNormalTimeFormat() { return SkString("%6.2f"); } 250 251 /** 252 * Reports the configuration of this PictureRenderer. 253 */ getConfigName()254 SkString getConfigName() { 255 SkString config = this->getConfigNameInternal(); 256 if (!fViewport.isEmpty()) { 257 config.appendf("_viewport_%ix%i", fViewport.width(), fViewport.height()); 258 } 259 if (fScaleFactor != SK_Scalar1) { 260 config.appendf("_scalar_%f", SkScalarToFloat(fScaleFactor)); 261 } 262 if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) { 263 config.append("_rtree"); 264 } 265 #if SK_SUPPORT_GPU 266 switch (fDeviceType) { 267 case kGPU_DeviceType: 268 if (fSampleCount) { 269 config.appendf("_msaa%d", fSampleCount); 270 } else if (fUseDFText) { 271 config.append("_gpudft"); 272 } else { 273 config.append("_gpu"); 274 } 275 break; 276 case kNVPR_DeviceType: 277 config.appendf("_nvprmsaa%d", fSampleCount); 278 break; 279 #if SK_ANGLE 280 case kAngle_DeviceType: 281 config.append("_angle"); 282 break; 283 #endif 284 #if SK_MESA 285 case kMesa_DeviceType: 286 config.append("_mesa"); 287 break; 288 #endif 289 default: 290 // Assume that no extra info means bitmap. 291 break; 292 } 293 #endif 294 config.append(fDrawFiltersConfig.c_str()); 295 return config; 296 } 297 getJSONConfig()298 Json::Value getJSONConfig() { 299 Json::Value result; 300 301 result["mode"] = this->getConfigNameInternal().c_str(); 302 result["scale"] = 1.0f; 303 if (SK_Scalar1 != fScaleFactor) { 304 result["scale"] = SkScalarToFloat(fScaleFactor); 305 } 306 if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) { 307 result["bbh"] = "rtree"; 308 } 309 #if SK_SUPPORT_GPU 310 SkString tmp; 311 switch (fDeviceType) { 312 case kGPU_DeviceType: 313 if (0 != fSampleCount) { 314 tmp = "msaa"; 315 tmp.appendS32(fSampleCount); 316 result["config"] = tmp.c_str(); 317 } else if (fUseDFText) { 318 result["config"] = "gpudft"; 319 } else { 320 result["config"] = "gpu"; 321 } 322 break; 323 case kNVPR_DeviceType: 324 tmp = "nvprmsaa"; 325 tmp.appendS32(fSampleCount); 326 result["config"] = tmp.c_str(); 327 break; 328 #if SK_ANGLE 329 case kAngle_DeviceType: 330 result["config"] = "angle"; 331 break; 332 #endif 333 #if SK_MESA 334 case kMesa_DeviceType: 335 result["config"] = "mesa"; 336 break; 337 #endif 338 default: 339 // Assume that no extra info means bitmap. 340 break; 341 } 342 #endif 343 return result; 344 } 345 346 #if SK_SUPPORT_GPU isUsingGpuDevice()347 bool isUsingGpuDevice() { 348 switch (fDeviceType) { 349 case kGPU_DeviceType: 350 case kNVPR_DeviceType: 351 // fall through 352 #if SK_ANGLE 353 case kAngle_DeviceType: 354 // fall through 355 #endif 356 #if SK_MESA 357 case kMesa_DeviceType: 358 #endif 359 return true; 360 default: 361 return false; 362 } 363 } 364 getGLContext()365 SkGLContext* getGLContext() { 366 GrContextFactory::GLContextType glContextType 367 = GrContextFactory::kNull_GLContextType; 368 switch(fDeviceType) { 369 case kGPU_DeviceType: 370 glContextType = GrContextFactory::kNative_GLContextType; 371 break; 372 case kNVPR_DeviceType: 373 glContextType = GrContextFactory::kNVPR_GLContextType; 374 break; 375 #if SK_ANGLE 376 case kAngle_DeviceType: 377 glContextType = GrContextFactory::kANGLE_GLContextType; 378 break; 379 #endif 380 #if SK_MESA 381 case kMesa_DeviceType: 382 glContextType = GrContextFactory::kMESA_GLContextType; 383 break; 384 #endif 385 default: 386 return NULL; 387 } 388 return fGrContextFactory.getGLContext(glContextType); 389 } 390 getGrContext()391 GrContext* getGrContext() { 392 return fGrContext; 393 } 394 getGrContextOptions()395 const GrContext::Options& getGrContextOptions() { 396 return fGrContextFactory.getGlobalOptions(); 397 } 398 #endif 399 getCanvas()400 SkCanvas* getCanvas() { 401 return fCanvas; 402 } 403 getPicture()404 const SkPicture* getPicture() { 405 return fPicture; 406 } 407 408 #if SK_SUPPORT_GPU PictureRenderer(const GrContext::Options & opts)409 explicit PictureRenderer(const GrContext::Options &opts) 410 #else 411 PictureRenderer() 412 #endif 413 : fJsonSummaryPtr(NULL) 414 , fDeviceType(kBitmap_DeviceType) 415 , fEnableWrites(false) 416 , fBBoxHierarchyType(kNone_BBoxHierarchyType) 417 , fScaleFactor(SK_Scalar1) 418 #if SK_SUPPORT_GPU 419 , fGrContextFactory(opts) 420 , fGrContext(NULL) 421 , fSampleCount(0) 422 , fUseDFText(false) 423 #endif 424 { 425 sk_bzero(fDrawFilters, sizeof(fDrawFilters)); 426 fViewport.set(0, 0); 427 } 428 429 #if SK_SUPPORT_GPU ~PictureRenderer()430 virtual ~PictureRenderer() { 431 SkSafeUnref(fGrContext); 432 } 433 #endif 434 435 protected: 436 SkAutoTUnref<SkCanvas> fCanvas; 437 SkAutoTUnref<const SkPicture> fPicture; 438 bool fUseChecksumBasedFilenames; 439 bool fUseMultiPictureDraw; 440 ImageResultsAndExpectations* fJsonSummaryPtr; 441 SkDeviceTypes fDeviceType; 442 bool fEnableWrites; 443 BBoxHierarchyType fBBoxHierarchyType; 444 DrawFilterFlags fDrawFilters[SkDrawFilter::kTypeCount]; 445 SkString fDrawFiltersConfig; 446 SkString fWritePath; 447 SkString fMismatchPath; 448 SkString fInputFilename; 449 450 void buildBBoxHierarchy(); 451 452 /** 453 * Return the total width that should be drawn. If the viewport width has been set greater than 454 * 0, this will be the minimum of the current SkPicture's width and the viewport's width. 455 */ 456 int getViewWidth(); 457 458 /** 459 * Return the total height that should be drawn. If the viewport height has been set greater 460 * than 0, this will be the minimum of the current SkPicture's height and the viewport's height. 461 */ 462 int getViewHeight(); 463 464 /** 465 * Scales the provided canvas to the scale factor set by setScaleFactor. 466 */ 467 void scaleToScaleFactor(SkCanvas*); 468 469 SkBBHFactory* getFactory(); recordFlags()470 uint32_t recordFlags() const { return 0; } 471 SkCanvas* setupCanvas(); 472 virtual SkCanvas* setupCanvas(int width, int height); 473 474 /** 475 * Copy src to dest; if src==NULL, set dest to empty string. 476 */ 477 static void CopyString(SkString* dest, const SkString* src); 478 479 private: 480 SkISize fViewport; 481 SkScalar fScaleFactor; 482 #if SK_SUPPORT_GPU 483 GrContextFactory fGrContextFactory; 484 GrContext* fGrContext; 485 int fSampleCount; 486 bool fUseDFText; 487 #endif 488 489 virtual SkString getConfigNameInternal() = 0; 490 491 typedef SkRefCnt INHERITED; 492 }; 493 494 /** 495 * This class does not do any rendering, but its render function executes recording, which we want 496 * to time. 497 */ 498 class RecordPictureRenderer : public PictureRenderer { 499 public: 500 #if SK_SUPPORT_GPU RecordPictureRenderer(const GrContext::Options & opts)501 RecordPictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } 502 #endif 503 504 bool render(SkBitmap** out = NULL) override; 505 getPerIterTimeFormat()506 SkString getPerIterTimeFormat() override { return SkString("%.4f"); } 507 getNormalTimeFormat()508 SkString getNormalTimeFormat() override { return SkString("%6.4f"); } 509 510 protected: 511 SkCanvas* setupCanvas(int width, int height) override; 512 513 private: 514 SkString getConfigNameInternal() override; 515 516 typedef PictureRenderer INHERITED; 517 }; 518 519 class PipePictureRenderer : public PictureRenderer { 520 public: 521 #if SK_SUPPORT_GPU PipePictureRenderer(const GrContext::Options & opts)522 PipePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } 523 #endif 524 525 bool render(SkBitmap** out = NULL) override; 526 527 private: 528 SkString getConfigNameInternal() override; 529 530 typedef PictureRenderer INHERITED; 531 }; 532 533 class SimplePictureRenderer : public PictureRenderer { 534 public: 535 #if SK_SUPPORT_GPU SimplePictureRenderer(const GrContext::Options & opts)536 SimplePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } 537 #endif 538 539 virtual void init(const SkPicture* pict, 540 const SkString* writePath, 541 const SkString* mismatchPath, 542 const SkString* inputFilename, 543 bool useChecksumBasedFilenames, 544 bool useMultiPictureDraw) override; 545 546 bool render(SkBitmap** out = NULL) override; 547 548 private: 549 SkString getConfigNameInternal() override; 550 551 typedef PictureRenderer INHERITED; 552 }; 553 554 class TiledPictureRenderer : public PictureRenderer { 555 public: 556 #if SK_SUPPORT_GPU 557 TiledPictureRenderer(const GrContext::Options &opts); 558 #else 559 TiledPictureRenderer(); 560 #endif 561 562 virtual void init(const SkPicture* pict, 563 const SkString* writePath, 564 const SkString* mismatchPath, 565 const SkString* inputFilename, 566 bool useChecksumBasedFilenames, 567 bool useMultiPictureDraw) override; 568 569 /** 570 * Renders to tiles, rather than a single canvas. 571 * If fWritePath was provided, a separate file is 572 * created for each tile, named "path0.png", "path1.png", etc. 573 */ 574 bool render(SkBitmap** out = NULL) override; 575 576 void end() override; 577 setTileWidth(int width)578 void setTileWidth(int width) { 579 fTileWidth = width; 580 } 581 getTileWidth()582 int getTileWidth() const { 583 return fTileWidth; 584 } 585 setTileHeight(int height)586 void setTileHeight(int height) { 587 fTileHeight = height; 588 } 589 getTileHeight()590 int getTileHeight() const { 591 return fTileHeight; 592 } 593 setTileWidthPercentage(double percentage)594 void setTileWidthPercentage(double percentage) { 595 fTileWidthPercentage = percentage; 596 } 597 getTileWidthPercentage()598 double getTileWidthPercentage() const { 599 return fTileWidthPercentage; 600 } 601 setTileHeightPercentage(double percentage)602 void setTileHeightPercentage(double percentage) { 603 fTileHeightPercentage = percentage; 604 } 605 getTileHeightPercentage()606 double getTileHeightPercentage() const { 607 return fTileHeightPercentage; 608 } 609 setTileMinPowerOf2Width(int width)610 void setTileMinPowerOf2Width(int width) { 611 SkASSERT(SkIsPow2(width) && width > 0); 612 if (!SkIsPow2(width) || width <= 0) { 613 return; 614 } 615 616 fTileMinPowerOf2Width = width; 617 } 618 getTileMinPowerOf2Width()619 int getTileMinPowerOf2Width() const { 620 return fTileMinPowerOf2Width; 621 } 622 getTiledRenderer()623 TiledPictureRenderer* getTiledRenderer() override { return this; } 624 supportsTimingIndividualTiles()625 virtual bool supportsTimingIndividualTiles() { return true; } 626 627 /** 628 * Report the number of tiles in the x and y directions. Must not be called before init. 629 * @param x Output parameter identifying the number of tiles in the x direction. 630 * @param y Output parameter identifying the number of tiles in the y direction. 631 * @return True if the tiles have been set up, and x and y are meaningful. If false, x and y are 632 * unmodified. 633 */ 634 bool tileDimensions(int& x, int&y); 635 636 /** 637 * Move to the next tile and return its indices. Must be called before calling drawCurrentTile 638 * for the first time. 639 * @param i Output parameter identifying the column of the next tile to be drawn on the next 640 * call to drawNextTile. 641 * @param j Output parameter identifying the row of the next tile to be drawn on the next call 642 * to drawNextTile. 643 * @param True if the tiles have been created and the next tile to be drawn by drawCurrentTile 644 * is within the range of tiles. If false, i and j are unmodified. 645 */ 646 bool nextTile(int& i, int& j); 647 648 /** 649 * Render one tile. This will draw the same tile each time it is called until nextTile is 650 * called. The tile rendered will depend on how many calls have been made to nextTile. 651 * It is an error to call this without first calling nextTile, or if nextTile returns false. 652 */ 653 void drawCurrentTile(); 654 655 protected: 656 SkTDArray<SkIRect> fTileRects; 657 658 SkCanvas* setupCanvas(int width, int height) override; 659 SkString getConfigNameInternal() override; 660 661 private: 662 int fTileWidth; 663 int fTileHeight; 664 double fTileWidthPercentage; 665 double fTileHeightPercentage; 666 int fTileMinPowerOf2Width; 667 668 // These variables are only used for timing individual tiles. 669 // Next tile to draw in fTileRects. 670 int fCurrentTileOffset; 671 // Number of tiles in the x direction. 672 int fTilesX; 673 // Number of tiles in the y direction. 674 int fTilesY; 675 676 void setupTiles(); 677 void setupPowerOf2Tiles(); 678 bool postRender(SkCanvas*, const SkIRect& tileRect, 679 SkBitmap* tempBM, SkBitmap** out, 680 int tileNumber); 681 682 typedef PictureRenderer INHERITED; 683 }; 684 685 /** 686 * This class does not do any rendering, but its render function executes turning an SkPictureRecord 687 * into an SkPicturePlayback, which we want to time. 688 */ 689 class PlaybackCreationRenderer : public PictureRenderer { 690 public: 691 #if SK_SUPPORT_GPU PlaybackCreationRenderer(const GrContext::Options & opts)692 PlaybackCreationRenderer(const GrContext::Options &opts) : INHERITED(opts) { } 693 #endif 694 695 void setup() override; 696 697 bool render(SkBitmap** out = NULL) override; 698 getPerIterTimeFormat()699 SkString getPerIterTimeFormat() override { return SkString("%.4f"); } 700 getNormalTimeFormat()701 SkString getNormalTimeFormat() override { return SkString("%6.4f"); } 702 703 private: 704 SkAutoTDelete<SkPictureRecorder> fRecorder; 705 706 SkString getConfigNameInternal() override; 707 708 typedef PictureRenderer INHERITED; 709 }; 710 711 #if SK_SUPPORT_GPU 712 extern PictureRenderer* CreateGatherPixelRefsRenderer(const GrContext::Options& opts); 713 #else 714 extern PictureRenderer* CreateGatherPixelRefsRenderer(); 715 #endif 716 717 } 718 719 #endif // PictureRenderer_DEFINED 720