1 /* 2 * Copyright 2010 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 GrContext_DEFINED 9 #define GrContext_DEFINED 10 11 #include "GrClip.h" 12 #include "GrColor.h" 13 #include "GrPaint.h" 14 #include "GrPathRendererChain.h" 15 #include "GrRenderTarget.h" 16 #include "GrTextureProvider.h" 17 #include "SkMatrix.h" 18 #include "SkPathEffect.h" 19 #include "SkTypes.h" 20 21 class GrAARectRenderer; 22 class GrBatchFontCache; 23 class GrDrawTarget; 24 class GrFragmentProcessor; 25 class GrGpu; 26 class GrGpuTraceMarker; 27 class GrIndexBuffer; 28 class GrLayerCache; 29 class GrOvalRenderer; 30 class GrPath; 31 class GrPathRenderer; 32 class GrPipelineBuilder; 33 class GrResourceEntry; 34 class GrResourceCache; 35 class GrResourceProvider; 36 class GrTestTarget; 37 class GrTextBlobCache; 38 class GrTextContext; 39 class GrTextureParams; 40 class GrVertexBuffer; 41 class GrStrokeInfo; 42 class GrSoftwarePathRenderer; 43 class SkGpuDevice; 44 45 class SK_API GrContext : public SkRefCnt { 46 public: 47 SK_DECLARE_INST_COUNT(GrContext) 48 49 struct Options { OptionsOptions50 Options() : fDrawPathToCompressedTexture(false), fSuppressPrints(false) { } 51 52 // EXPERIMENTAL 53 // May be removed in the future, or may become standard depending 54 // on the outcomes of a variety of internal tests. 55 bool fDrawPathToCompressedTexture; 56 bool fSuppressPrints; 57 }; 58 59 /** 60 * Creates a GrContext for a backend context. 61 */ 62 static GrContext* Create(GrBackend, GrBackendContext, const Options* opts = NULL); 63 64 /** 65 * Only defined in test apps. 66 */ 67 static GrContext* CreateMockContext(); 68 69 virtual ~GrContext(); 70 71 /** 72 * The GrContext normally assumes that no outsider is setting state 73 * within the underlying 3D API's context/device/whatever. This call informs 74 * the context that the state was modified and it should resend. Shouldn't 75 * be called frequently for good performance. 76 * The flag bits, state, is dpendent on which backend is used by the 77 * context, either GL or D3D (possible in future). 78 */ 79 void resetContext(uint32_t state = kAll_GrBackendState); 80 81 /** 82 * Callback function to allow classes to cleanup on GrContext destruction. 83 * The 'info' field is filled in with the 'info' passed to addCleanUp. 84 */ 85 typedef void (*PFCleanUpFunc)(const GrContext* context, void* info); 86 87 /** 88 * Add a function to be called from within GrContext's destructor. 89 * This gives classes a chance to free resources held on a per context basis. 90 * The 'info' parameter will be stored and passed to the callback function. 91 */ addCleanUp(PFCleanUpFunc cleanUp,void * info)92 void addCleanUp(PFCleanUpFunc cleanUp, void* info) { 93 CleanUpData* entry = fCleanUpData.push(); 94 95 entry->fFunc = cleanUp; 96 entry->fInfo = info; 97 } 98 99 /** 100 * Abandons all GPU resources and assumes the underlying backend 3D API 101 * context is not longer usable. Call this if you have lost the associated 102 * GPU context, and thus internal texture, buffer, etc. references/IDs are 103 * now invalid. Should be called even when GrContext is no longer going to 104 * be used for two reasons: 105 * 1) ~GrContext will not try to free the objects in the 3D API. 106 * 2) Any GrGpuResources created by this GrContext that outlive 107 * will be marked as invalid (GrGpuResource::wasDestroyed()) and 108 * when they're destroyed no 3D API calls will be made. 109 * Content drawn since the last GrContext::flush() may be lost. After this 110 * function is called the only valid action on the GrContext or 111 * GrGpuResources it created is to destroy them. 112 */ 113 void abandonContext(); 114 115 /////////////////////////////////////////////////////////////////////////// 116 // Resource Cache 117 118 /** 119 * Return the current GPU resource cache limits. 120 * 121 * @param maxResources If non-null, returns maximum number of resources that 122 * can be held in the cache. 123 * @param maxResourceBytes If non-null, returns maximum number of bytes of 124 * video memory that can be held in the cache. 125 */ 126 void getResourceCacheLimits(int* maxResources, size_t* maxResourceBytes) const; 127 128 /** 129 * Gets the current GPU resource cache usage. 130 * 131 * @param resourceCount If non-null, returns the number of resources that are held in the 132 * cache. 133 * @param maxResourceBytes If non-null, returns the total number of bytes of video memory held 134 * in the cache. 135 */ 136 void getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const; 137 138 /** 139 * Specify the GPU resource cache limits. If the current cache exceeds either 140 * of these, it will be purged (LRU) to keep the cache within these limits. 141 * 142 * @param maxResources The maximum number of resources that can be held in 143 * the cache. 144 * @param maxResourceBytes The maximum number of bytes of video memory 145 * that can be held in the cache. 146 */ 147 void setResourceCacheLimits(int maxResources, size_t maxResourceBytes); 148 textureProvider()149 GrTextureProvider* textureProvider() { return fTextureProvider; } textureProvider()150 const GrTextureProvider* textureProvider() const { return fTextureProvider; } 151 152 /** 153 * Frees GPU created by the context. Can be called to reduce GPU memory 154 * pressure. 155 */ 156 void freeGpuResources(); 157 158 /** 159 * Purge all the unlocked resources from the cache. 160 * This entry point is mainly meant for timing texture uploads 161 * and is not defined in normal builds of Skia. 162 */ 163 void purgeAllUnlockedResources(); 164 165 ////////////////////////////////////////////////////////////////////////// 166 /// Texture and Render Target Queries 167 168 /** 169 * Can the provided configuration act as a texture? 170 */ 171 bool isConfigTexturable(GrPixelConfig) const; 172 173 /** 174 * Can non-power-of-two textures be used with tile modes other than clamp? 175 */ 176 bool npotTextureTileSupport() const; 177 178 /** 179 * Return the max width or height of a texture supported by the current GPU. 180 */ 181 int getMaxTextureSize() const; 182 183 /** 184 * Temporarily override the true max texture size. Note: an override 185 * larger then the true max texture size will have no effect. 186 * This entry point is mainly meant for testing texture size dependent 187 * features and is only available if defined outside of Skia (see 188 * bleed GM. 189 */ 190 void setMaxTextureSizeOverride(int maxTextureSizeOverride); 191 192 /** 193 * Can the provided configuration act as a color render target? 194 */ 195 bool isConfigRenderable(GrPixelConfig config, bool withMSAA) const; 196 197 /** 198 * Return the max width or height of a render target supported by the 199 * current GPU. 200 */ 201 int getMaxRenderTargetSize() const; 202 203 /** 204 * Returns the max sample count for a render target. It will be 0 if MSAA 205 * is not supported. 206 */ 207 int getMaxSampleCount() const; 208 209 /** 210 * Returns the recommended sample count for a render target when using this 211 * context. 212 * 213 * @param config the configuration of the render target. 214 * @param dpi the display density in dots per inch. 215 * 216 * @return sample count that should be perform well and have good enough 217 * rendering quality for the display. Alternatively returns 0 if 218 * MSAA is not supported or recommended to be used by default. 219 */ 220 int getRecommendedSampleCount(GrPixelConfig config, SkScalar dpi) const; 221 222 /////////////////////////////////////////////////////////////////////////// 223 // Draws 224 225 /** 226 * Clear the entire or rect of the render target, ignoring any clips. 227 * @param rect the rect to clear or the whole thing if rect is NULL. 228 * @param color the color to clear to. 229 * @param canIgnoreRect allows partial clears to be converted to whole 230 * clears on platforms for which that is cheap 231 * @param target The render target to clear. 232 */ 233 void clear(const SkIRect* rect, GrColor color, bool canIgnoreRect, GrRenderTarget* target); 234 235 /** 236 * Draw everywhere (respecting the clip) with the paint. 237 */ 238 void drawPaint(GrRenderTarget*, const GrClip&, const GrPaint&, const SkMatrix& viewMatrix); 239 240 /** 241 * Draw the rect using a paint. 242 * @param paint describes how to color pixels. 243 * @param viewMatrix transformation matrix 244 * @param strokeInfo the stroke information (width, join, cap), and. 245 * the dash information (intervals, count, phase). 246 * If strokeInfo == NULL, then the rect is filled. 247 * Otherwise, if stroke width == 0, then the stroke 248 * is always a single pixel thick, else the rect is 249 * mitered/beveled stroked based on stroke width. 250 * The rects coords are used to access the paint (through texture matrix) 251 */ 252 void drawRect(GrRenderTarget*, 253 const GrClip&, 254 const GrPaint& paint, 255 const SkMatrix& viewMatrix, 256 const SkRect&, 257 const GrStrokeInfo* strokeInfo = NULL); 258 259 /** 260 * Maps a rectangle of shader coordinates to a rectangle and draws that rectangle 261 * 262 * @param paint describes how to color pixels. 263 * @param viewMatrix transformation matrix which applies to rectToDraw 264 * @param rectToDraw the rectangle to draw 265 * @param localRect the rectangle of shader coordinates applied to rectToDraw 266 * @param localMatrix an optional matrix to transform the shader coordinates before applying 267 * to rectToDraw 268 */ 269 void drawNonAARectToRect(GrRenderTarget*, 270 const GrClip&, 271 const GrPaint& paint, 272 const SkMatrix& viewMatrix, 273 const SkRect& rectToDraw, 274 const SkRect& localRect, 275 const SkMatrix* localMatrix = NULL); 276 277 /** 278 * Draws a non-AA rect with paint and a localMatrix 279 */ drawNonAARectWithLocalMatrix(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkMatrix & viewMatrix,const SkRect & rect,const SkMatrix & localMatrix)280 void drawNonAARectWithLocalMatrix(GrRenderTarget* rt, 281 const GrClip& clip, 282 const GrPaint& paint, 283 const SkMatrix& viewMatrix, 284 const SkRect& rect, 285 const SkMatrix& localMatrix) { 286 this->drawNonAARectToRect(rt, clip, paint, viewMatrix, rect, rect, &localMatrix); 287 } 288 289 /** 290 * Draw a roundrect using a paint. 291 * 292 * @param paint describes how to color pixels. 293 * @param viewMatrix transformation matrix 294 * @param rrect the roundrect to draw 295 * @param strokeInfo the stroke information (width, join, cap) and 296 * the dash information (intervals, count, phase). 297 */ 298 void drawRRect(GrRenderTarget*, 299 const GrClip&, 300 const GrPaint&, 301 const SkMatrix& viewMatrix, 302 const SkRRect& rrect, 303 const GrStrokeInfo&); 304 305 /** 306 * Shortcut for drawing an SkPath consisting of nested rrects using a paint. 307 * Does not support stroking. The result is undefined if outer does not contain 308 * inner. 309 * 310 * @param paint describes how to color pixels. 311 * @param viewMatrix transformation matrix 312 * @param outer the outer roundrect 313 * @param inner the inner roundrect 314 */ 315 void drawDRRect(GrRenderTarget*, 316 const GrClip&, 317 const GrPaint&, 318 const SkMatrix& viewMatrix, 319 const SkRRect& outer, 320 const SkRRect& inner); 321 322 323 /** 324 * Draws a path. 325 * 326 * @param paint describes how to color pixels. 327 * @param viewMatrix transformation matrix 328 * @param path the path to draw 329 * @param strokeInfo the stroke information (width, join, cap) and 330 * the dash information (intervals, count, phase). 331 */ 332 void drawPath(GrRenderTarget*, 333 const GrClip&, 334 const GrPaint&, 335 const SkMatrix& viewMatrix, 336 const SkPath&, 337 const GrStrokeInfo&); 338 339 /** 340 * Draws vertices with a paint. 341 * 342 * @param paint describes how to color pixels. 343 * @param viewMatrix transformation matrix 344 * @param primitiveType primitives type to draw. 345 * @param vertexCount number of vertices. 346 * @param positions array of vertex positions, required. 347 * @param texCoords optional array of texture coordinates used 348 * to access the paint. 349 * @param colors optional array of per-vertex colors, supercedes 350 * the paint's color field. 351 * @param indices optional array of indices. If NULL vertices 352 * are drawn non-indexed. 353 * @param indexCount if indices is non-null then this is the 354 * number of indices. 355 */ 356 void drawVertices(GrRenderTarget*, 357 const GrClip&, 358 const GrPaint& paint, 359 const SkMatrix& viewMatrix, 360 GrPrimitiveType primitiveType, 361 int vertexCount, 362 const SkPoint positions[], 363 const SkPoint texs[], 364 const GrColor colors[], 365 const uint16_t indices[], 366 int indexCount); 367 368 /** 369 * Draws an oval. 370 * 371 * @param paint describes how to color pixels. 372 * @param viewMatrix transformation matrix 373 * @param oval the bounding rect of the oval. 374 * @param strokeInfo the stroke information (width, join, cap) and 375 * the dash information (intervals, count, phase). 376 */ 377 void drawOval(GrRenderTarget*, 378 const GrClip&, 379 const GrPaint& paint, 380 const SkMatrix& viewMatrix, 381 const SkRect& oval, 382 const GrStrokeInfo& strokeInfo); 383 384 /////////////////////////////////////////////////////////////////////////// 385 // Misc. 386 387 /** 388 * Flags that affect flush() behavior. 389 */ 390 enum FlushBits { 391 /** 392 * A client may reach a point where it has partially rendered a frame 393 * through a GrContext that it knows the user will never see. This flag 394 * causes the flush to skip submission of deferred content to the 3D API 395 * during the flush. 396 */ 397 kDiscard_FlushBit = 0x2, 398 }; 399 400 /** 401 * Call to ensure all drawing to the context has been issued to the 402 * underlying 3D API. 403 * @param flagsBitfield flags that control the flushing behavior. See 404 * FlushBits. 405 */ 406 void flush(int flagsBitfield = 0); 407 408 /** 409 * These flags can be used with the read/write pixels functions below. 410 */ 411 enum PixelOpsFlags { 412 /** The GrContext will not be flushed before the surface read or write. This means that 413 the read or write may occur before previous draws have executed. */ 414 kDontFlush_PixelOpsFlag = 0x1, 415 /** Any surface writes should be flushed to the backend 3D API after the surface operation 416 is complete */ 417 kFlushWrites_PixelOp = 0x2, 418 /** The src for write or dst read is unpremultiplied. This is only respected if both the 419 config src and dst configs are an RGBA/BGRA 8888 format. */ 420 kUnpremul_PixelOpsFlag = 0x4, 421 }; 422 423 /** 424 * Reads a rectangle of pixels from a render target. 425 * @param target the render target to read from. 426 * @param left left edge of the rectangle to read (inclusive) 427 * @param top top edge of the rectangle to read (inclusive) 428 * @param width width of rectangle to read in pixels. 429 * @param height height of rectangle to read in pixels. 430 * @param config the pixel config of the destination buffer 431 * @param buffer memory to read the rectangle into. 432 * @param rowBytes number of bytes bewtween consecutive rows. Zero means rows are tightly 433 * packed. 434 * @param pixelOpsFlags see PixelOpsFlags enum above. 435 * 436 * @return true if the read succeeded, false if not. The read can fail because of an unsupported 437 * pixel config or because no render target is currently set and NULL was passed for 438 * target. 439 */ 440 bool readRenderTargetPixels(GrRenderTarget* target, 441 int left, int top, int width, int height, 442 GrPixelConfig config, void* buffer, 443 size_t rowBytes = 0, 444 uint32_t pixelOpsFlags = 0); 445 446 /** 447 * Writes a rectangle of pixels to a surface. 448 * @param surface the surface to write to. 449 * @param left left edge of the rectangle to write (inclusive) 450 * @param top top edge of the rectangle to write (inclusive) 451 * @param width width of rectangle to write in pixels. 452 * @param height height of rectangle to write in pixels. 453 * @param config the pixel config of the source buffer 454 * @param buffer memory to read pixels from 455 * @param rowBytes number of bytes between consecutive rows. Zero 456 * means rows are tightly packed. 457 * @param pixelOpsFlags see PixelOpsFlags enum above. 458 * @return true if the write succeeded, false if not. The write can fail because of an 459 * unsupported combination of surface and src configs. 460 */ 461 bool writeSurfacePixels(GrSurface* surface, 462 int left, int top, int width, int height, 463 GrPixelConfig config, const void* buffer, 464 size_t rowBytes, 465 uint32_t pixelOpsFlags = 0); 466 467 /** 468 * Copies a rectangle of texels from src to dst. 469 * bounds. 470 * @param dst the surface to copy to. 471 * @param src the surface to copy from. 472 * @param srcRect the rectangle of the src that should be copied. 473 * @param dstPoint the translation applied when writing the srcRect's pixels to the dst. 474 * @param pixelOpsFlags see PixelOpsFlags enum above. (kUnpremul_PixelOpsFlag is not allowed). 475 */ 476 void copySurface(GrSurface* dst, 477 GrSurface* src, 478 const SkIRect& srcRect, 479 const SkIPoint& dstPoint, 480 uint32_t pixelOpsFlags = 0); 481 482 /** Helper that copies the whole surface but fails when the two surfaces are not identically 483 sized. */ copySurface(GrSurface * dst,GrSurface * src)484 bool copySurface(GrSurface* dst, GrSurface* src) { 485 if (NULL == dst || NULL == src || dst->width() != src->width() || 486 dst->height() != src->height()) { 487 return false; 488 } 489 this->copySurface(dst, src, SkIRect::MakeWH(dst->width(), dst->height()), 490 SkIPoint::Make(0,0)); 491 return true; 492 } 493 494 /** 495 * After this returns any pending writes to the surface will have been issued to the backend 3D API. 496 */ 497 void flushSurfaceWrites(GrSurface* surface); 498 499 /** 500 * Equivalent to flushSurfaceWrites but also performs MSAA resolve if necessary. This call is 501 * used to make the surface contents available to be read in the backend 3D API, usually for a 502 * compositing step external to Skia. 503 * 504 * It is not necessary to call this before reading the render target via Skia/GrContext. 505 * GrContext will detect when it must perform a resolve before reading pixels back from the 506 * surface or using it as a texture. 507 */ 508 void prepareSurfaceForExternalRead(GrSurface*); 509 510 /** 511 * Provides a perfomance hint that the render target's contents are allowed 512 * to become undefined. 513 */ 514 void discardRenderTarget(GrRenderTarget*); 515 516 /** 517 * An ID associated with this context, guaranteed to be unique. 518 */ uniqueID()519 uint32_t uniqueID() { return fUniqueID; } 520 521 /////////////////////////////////////////////////////////////////////////// 522 // Functions intended for internal use only. getGpu()523 GrGpu* getGpu() { return fGpu; } getGpu()524 const GrGpu* getGpu() const { return fGpu; } getBatchFontCache()525 GrBatchFontCache* getBatchFontCache() { return fBatchFontCache; } getLayerCache()526 GrLayerCache* getLayerCache() { return fLayerCache.get(); } getTextBlobCache()527 GrTextBlobCache* getTextBlobCache() { return fTextBlobCache; } 528 GrDrawTarget* getTextTarget(); getAARectRenderer()529 GrAARectRenderer* getAARectRenderer() { return fAARectRenderer; } resourceProvider()530 GrResourceProvider* resourceProvider() { return fResourceProvider; } resourceProvider()531 const GrResourceProvider* resourceProvider() const { return fResourceProvider; } getResourceCache()532 GrResourceCache* getResourceCache() { return fResourceCache; } suppressPrints()533 bool suppressPrints() const { return fOptions.fSuppressPrints; } 534 535 // Called by tests that draw directly to the context via GrDrawTarget 536 void getTestTarget(GrTestTarget*); 537 538 void addGpuTraceMarker(const GrGpuTraceMarker* marker); 539 void removeGpuTraceMarker(const GrGpuTraceMarker* marker); 540 541 GrPathRenderer* getPathRenderer( 542 const GrDrawTarget* target, 543 const GrPipelineBuilder*, 544 const SkMatrix& viewMatrix, 545 const SkPath& path, 546 const GrStrokeInfo& stroke, 547 bool allowSW, 548 GrPathRendererChain::DrawType drawType = GrPathRendererChain::kColor_DrawType, 549 GrPathRendererChain::StencilSupport* stencilSupport = NULL); 550 551 /** 552 * This returns a copy of the the GrContext::Options that was passed to the 553 * constructor of this class. 554 */ getOptions()555 const Options& getOptions() const { return fOptions; } 556 557 /** Prints cache stats to the string if GR_CACHE_STATS == 1. */ 558 void dumpCacheStats(SkString*) const; 559 void printCacheStats() const; 560 561 /** Prints GPU stats to the string if GR_GPU_STATS == 1. */ 562 void dumpGpuStats(SkString*) const; 563 void printGpuStats() const; 564 565 private: 566 GrGpu* fGpu; 567 GrResourceCache* fResourceCache; 568 // this union exists because the inheritance of GrTextureProvider->GrResourceProvider 569 // is in a private header. 570 union { 571 GrResourceProvider* fResourceProvider; 572 GrTextureProvider* fTextureProvider; 573 }; 574 575 GrBatchFontCache* fBatchFontCache; 576 SkAutoTDelete<GrLayerCache> fLayerCache; 577 SkAutoTDelete<GrTextBlobCache> fTextBlobCache; 578 579 GrPathRendererChain* fPathRendererChain; 580 GrSoftwarePathRenderer* fSoftwarePathRenderer; 581 582 GrDrawTarget* fDrawBuffer; 583 584 // Set by OverbudgetCB() to request that GrContext flush before exiting a draw. 585 bool fFlushToReduceCacheSize; 586 GrAARectRenderer* fAARectRenderer; 587 GrOvalRenderer* fOvalRenderer; 588 589 bool fDidTestPMConversions; 590 int fPMToUPMConversion; 591 int fUPMToPMConversion; 592 593 struct CleanUpData { 594 PFCleanUpFunc fFunc; 595 void* fInfo; 596 }; 597 598 SkTDArray<CleanUpData> fCleanUpData; 599 600 int fMaxTextureSizeOverride; 601 602 const Options fOptions; 603 const uint32_t fUniqueID; 604 605 GrContext(const Options&); // init must be called after the constructor. 606 bool init(GrBackend, GrBackendContext); 607 void initMockContext(); 608 void initCommon(); 609 610 class AutoCheckFlush; 611 // Sets the paint and returns the target to draw into. 612 GrDrawTarget* prepareToDraw(GrPipelineBuilder*, 613 GrRenderTarget* rt, 614 const GrClip&, 615 const GrPaint* paint, 616 const AutoCheckFlush*); 617 618 // A simpler version of the above which just returns the draw target. Clip is *NOT* set 619 GrDrawTarget* prepareToDraw(); 620 621 void internalDrawPath(GrDrawTarget*, 622 GrPipelineBuilder*, 623 const SkMatrix& viewMatrix, 624 GrColor, 625 bool useAA, 626 const SkPath&, 627 const GrStrokeInfo&); 628 629 /** 630 * Creates a new text rendering context that is optimal for the 631 * render target and the context. Caller assumes the ownership 632 * of the returned object. The returned object must be deleted 633 * before the context is destroyed. 634 * TODO we can possibly bury this behind context, but we need to be able to use the 635 * drawText_asPaths logic on SkGpuDevice 636 */ 637 GrTextContext* createTextContext(GrRenderTarget*, 638 SkGpuDevice*, 639 const SkDeviceProperties&, 640 bool enableDistanceFieldFonts); 641 642 643 /** 644 * These functions create premul <-> unpremul effects if it is possible to generate a pair 645 * of effects that make a readToUPM->writeToPM->readToUPM cycle invariant. Otherwise, they 646 * return NULL. 647 */ 648 const GrFragmentProcessor* createPMToUPMEffect(GrTexture*, bool swapRAndB, const SkMatrix&); 649 const GrFragmentProcessor* createUPMToPMEffect(GrTexture*, bool swapRAndB, const SkMatrix&); 650 651 /** 652 * This callback allows the resource cache to callback into the GrContext 653 * when the cache is still over budget after a purge. 654 */ 655 static void OverBudgetCB(void* data); 656 657 /** 658 * A callback similar to the above for use by the TextBlobCache 659 * TODO move textblob draw calls below context so we can use the call above. 660 */ 661 static void TextBlobCacheOverBudgetCB(void* data); 662 663 // TODO see note on createTextContext 664 friend class SkGpuDevice; 665 666 typedef SkRefCnt INHERITED; 667 }; 668 669 #endif 670