OLD | NEW |
1 | 1 |
2 /* | 2 /* |
3 * Copyright 2015 Google Inc. | 3 * Copyright 2015 Google Inc. |
4 * | 4 * |
5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
7 */ | 7 */ |
8 | 8 |
9 #include "GrAARectRenderer.h" | 9 #include "GrAARectRenderer.h" |
10 #include "GrAtlasTextContext.h" | 10 #include "GrAtlasTextContext.h" |
11 #include "GrBatchTest.h" | 11 #include "GrBatchTest.h" |
12 #include "GrColor.h" | 12 #include "GrColor.h" |
13 #include "GrDefaultGeoProcFactory.h" | |
14 #include "GrDrawContext.h" | 13 #include "GrDrawContext.h" |
15 #include "GrOvalRenderer.h" | 14 #include "GrOvalRenderer.h" |
16 #include "GrPathRenderer.h" | 15 #include "GrPathRenderer.h" |
17 #include "GrRenderTarget.h" | 16 #include "GrRenderTarget.h" |
18 #include "GrRenderTargetPriv.h" | 17 #include "GrRenderTargetPriv.h" |
19 #include "GrStencilAndCoverTextContext.h" | 18 #include "GrStencilAndCoverTextContext.h" |
20 | 19 |
21 #include "batches/GrBatch.h" | 20 #include "batches/GrBatch.h" |
22 #include "batches/GrDrawAtlasBatch.h" | 21 #include "batches/GrDrawAtlasBatch.h" |
| 22 #include "batches/GrDrawVerticesBatch.h" |
23 #include "batches/GrStrokeRectBatch.h" | 23 #include "batches/GrStrokeRectBatch.h" |
24 | 24 |
25 #include "SkGr.h" | 25 #include "SkGr.h" |
26 #include "SkRSXform.h" | 26 #include "SkRSXform.h" |
27 | 27 |
28 #define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == fContext) | 28 #define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == fContext) |
29 #define RETURN_IF_ABANDONED if (!fDrawTarget) { return; } | 29 #define RETURN_IF_ABANDONED if (!fDrawTarget) { return; } |
30 #define RETURN_FALSE_IF_ABANDONED if (!fDrawTarget) { return false; } | 30 #define RETURN_FALSE_IF_ABANDONED if (!fDrawTarget) { return false; } |
31 #define RETURN_NULL_IF_ABANDONED if (!fDrawTarget) { return NULL; } | 31 #define RETURN_NULL_IF_ABANDONED if (!fDrawTarget) { return NULL; } |
32 | 32 |
(...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
366 | 366 |
367 GrPipelineBuilder pipelineBuilder(paint, rt, clip); | 367 GrPipelineBuilder pipelineBuilder(paint, rt, clip); |
368 fDrawTarget->drawBWRect(pipelineBuilder, | 368 fDrawTarget->drawBWRect(pipelineBuilder, |
369 paint.getColor(), | 369 paint.getColor(), |
370 viewMatrix, | 370 viewMatrix, |
371 rectToDraw, | 371 rectToDraw, |
372 &localRect, | 372 &localRect, |
373 localMatrix); | 373 localMatrix); |
374 } | 374 } |
375 | 375 |
376 static const GrGeometryProcessor* set_vertex_attributes(bool hasLocalCoords, | |
377 bool hasColors, | |
378 int* colorOffset, | |
379 int* texOffset, | |
380 GrColor color, | |
381 const SkMatrix& viewMatr
ix, | |
382 bool coverageIgnored) { | |
383 using namespace GrDefaultGeoProcFactory; | |
384 *texOffset = -1; | |
385 *colorOffset = -1; | |
386 Color gpColor(color); | |
387 if (hasColors) { | |
388 gpColor.fType = Color::kAttribute_Type; | |
389 } | |
390 | |
391 Coverage coverage(coverageIgnored ? Coverage::kNone_Type : Coverage::kSolid_
Type); | |
392 LocalCoords localCoords(hasLocalCoords ? LocalCoords::kHasExplicit_Type : | |
393 LocalCoords::kUsePosition_Type); | |
394 if (hasLocalCoords && hasColors) { | |
395 *colorOffset = sizeof(SkPoint); | |
396 *texOffset = sizeof(SkPoint) + sizeof(GrColor); | |
397 } else if (hasLocalCoords) { | |
398 *texOffset = sizeof(SkPoint); | |
399 } else if (hasColors) { | |
400 *colorOffset = sizeof(SkPoint); | |
401 } | |
402 return GrDefaultGeoProcFactory::Create(gpColor, coverage, localCoords, viewM
atrix); | |
403 } | |
404 | |
405 class DrawVerticesBatch : public GrBatch { | |
406 public: | |
407 struct Geometry { | |
408 GrColor fColor; | |
409 SkTDArray<SkPoint> fPositions; | |
410 SkTDArray<uint16_t> fIndices; | |
411 SkTDArray<GrColor> fColors; | |
412 SkTDArray<SkPoint> fLocalCoords; | |
413 }; | |
414 | |
415 static GrBatch* Create(const Geometry& geometry, GrPrimitiveType primitiveTy
pe, | |
416 const SkMatrix& viewMatrix, | |
417 const SkPoint* positions, int vertexCount, | |
418 const uint16_t* indices, int indexCount, | |
419 const GrColor* colors, const SkPoint* localCoords, | |
420 const SkRect& bounds) { | |
421 return SkNEW_ARGS(DrawVerticesBatch, (geometry, primitiveType, viewMatri
x, positions, | |
422 vertexCount, indices, indexCount,
colors, | |
423 localCoords, bounds)); | |
424 } | |
425 | |
426 const char* name() const override { return "DrawVerticesBatch"; } | |
427 | |
428 void getInvariantOutputColor(GrInitInvariantOutput* out) const override { | |
429 // When this is called on a batch, there is only one geometry bundle | |
430 if (this->hasColors()) { | |
431 out->setUnknownFourComponents(); | |
432 } else { | |
433 out->setKnownFourComponents(fGeoData[0].fColor); | |
434 } | |
435 } | |
436 | |
437 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override { | |
438 out->setKnownSingleComponent(0xff); | |
439 } | |
440 | |
441 void initBatchTracker(const GrPipelineInfo& init) override { | |
442 // Handle any color overrides | |
443 if (!init.readsColor()) { | |
444 fGeoData[0].fColor = GrColor_ILLEGAL; | |
445 } | |
446 init.getOverrideColorIfSet(&fGeoData[0].fColor); | |
447 | |
448 // setup batch properties | |
449 fBatch.fColorIgnored = !init.readsColor(); | |
450 fBatch.fColor = fGeoData[0].fColor; | |
451 fBatch.fUsesLocalCoords = init.readsLocalCoords(); | |
452 fBatch.fCoverageIgnored = !init.readsCoverage(); | |
453 } | |
454 | |
455 void generateGeometry(GrBatchTarget* batchTarget) override { | |
456 int colorOffset = -1, texOffset = -1; | |
457 SkAutoTUnref<const GrGeometryProcessor> gp( | |
458 set_vertex_attributes(this->hasLocalCoords(), this->hasColors(),
&colorOffset, | |
459 &texOffset, this->color(), this->viewMatri
x(), | |
460 this->coverageIgnored())); | |
461 | |
462 batchTarget->initDraw(gp, this->pipeline()); | |
463 | |
464 size_t vertexStride = gp->getVertexStride(); | |
465 | |
466 SkASSERT(vertexStride == sizeof(SkPoint) + (this->hasLocalCoords() ? siz
eof(SkPoint) : 0) | |
467 + (this->hasColors() ? sizeof(G
rColor) : 0)); | |
468 | |
469 int instanceCount = fGeoData.count(); | |
470 | |
471 const GrVertexBuffer* vertexBuffer; | |
472 int firstVertex; | |
473 | |
474 void* verts = batchTarget->makeVertSpace(vertexStride, this->vertexCount
(), | |
475 &vertexBuffer, &firstVertex); | |
476 | |
477 if (!verts) { | |
478 SkDebugf("Could not allocate vertices\n"); | |
479 return; | |
480 } | |
481 | |
482 const GrIndexBuffer* indexBuffer = NULL; | |
483 int firstIndex = 0; | |
484 | |
485 uint16_t* indices = NULL; | |
486 if (this->hasIndices()) { | |
487 indices = batchTarget->makeIndexSpace(this->indexCount(), &indexBuff
er, &firstIndex); | |
488 | |
489 if (!indices) { | |
490 SkDebugf("Could not allocate indices\n"); | |
491 return; | |
492 } | |
493 } | |
494 | |
495 int indexOffset = 0; | |
496 int vertexOffset = 0; | |
497 for (int i = 0; i < instanceCount; i++) { | |
498 const Geometry& args = fGeoData[i]; | |
499 | |
500 // TODO we can actually cache this interleaved and then just memcopy | |
501 if (this->hasIndices()) { | |
502 for (int j = 0; j < args.fIndices.count(); ++j, ++indexOffset) { | |
503 *(indices + indexOffset) = args.fIndices[j] + vertexOffset; | |
504 } | |
505 } | |
506 | |
507 for (int j = 0; j < args.fPositions.count(); ++j) { | |
508 *((SkPoint*)verts) = args.fPositions[j]; | |
509 if (this->hasColors()) { | |
510 *(GrColor*)((intptr_t)verts + colorOffset) = args.fColors[j]
; | |
511 } | |
512 if (this->hasLocalCoords()) { | |
513 *(SkPoint*)((intptr_t)verts + texOffset) = args.fLocalCoords
[j]; | |
514 } | |
515 verts = (void*)((intptr_t)verts + vertexStride); | |
516 vertexOffset++; | |
517 } | |
518 } | |
519 | |
520 GrVertices vertices; | |
521 if (this->hasIndices()) { | |
522 vertices.initIndexed(this->primitiveType(), vertexBuffer, indexBuffe
r, firstVertex, | |
523 firstIndex, this->vertexCount(), this->indexCou
nt()); | |
524 | |
525 } else { | |
526 vertices.init(this->primitiveType(), vertexBuffer, firstVertex, this
->vertexCount()); | |
527 } | |
528 batchTarget->draw(vertices); | |
529 } | |
530 | |
531 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } | |
532 | |
533 private: | |
534 DrawVerticesBatch(const Geometry& geometry, GrPrimitiveType primitiveType, | |
535 const SkMatrix& viewMatrix, | |
536 const SkPoint* positions, int vertexCount, | |
537 const uint16_t* indices, int indexCount, | |
538 const GrColor* colors, const SkPoint* localCoords, const S
kRect& bounds) { | |
539 this->initClassID<DrawVerticesBatch>(); | |
540 SkASSERT(positions); | |
541 | |
542 fBatch.fViewMatrix = viewMatrix; | |
543 Geometry& installedGeo = fGeoData.push_back(geometry); | |
544 | |
545 installedGeo.fPositions.append(vertexCount, positions); | |
546 if (indices) { | |
547 installedGeo.fIndices.append(indexCount, indices); | |
548 fBatch.fHasIndices = true; | |
549 } else { | |
550 fBatch.fHasIndices = false; | |
551 } | |
552 | |
553 if (colors) { | |
554 installedGeo.fColors.append(vertexCount, colors); | |
555 fBatch.fHasColors = true; | |
556 } else { | |
557 fBatch.fHasColors = false; | |
558 } | |
559 | |
560 if (localCoords) { | |
561 installedGeo.fLocalCoords.append(vertexCount, localCoords); | |
562 fBatch.fHasLocalCoords = true; | |
563 } else { | |
564 fBatch.fHasLocalCoords = false; | |
565 } | |
566 fBatch.fVertexCount = vertexCount; | |
567 fBatch.fIndexCount = indexCount; | |
568 fBatch.fPrimitiveType = primitiveType; | |
569 | |
570 this->setBounds(bounds); | |
571 } | |
572 | |
573 GrPrimitiveType primitiveType() const { return fBatch.fPrimitiveType; } | |
574 bool batchablePrimitiveType() const { | |
575 return kTriangles_GrPrimitiveType == fBatch.fPrimitiveType || | |
576 kLines_GrPrimitiveType == fBatch.fPrimitiveType || | |
577 kPoints_GrPrimitiveType == fBatch.fPrimitiveType; | |
578 } | |
579 GrColor color() const { return fBatch.fColor; } | |
580 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } | |
581 bool colorIgnored() const { return fBatch.fColorIgnored; } | |
582 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; } | |
583 bool hasColors() const { return fBatch.fHasColors; } | |
584 bool hasIndices() const { return fBatch.fHasIndices; } | |
585 bool hasLocalCoords() const { return fBatch.fHasLocalCoords; } | |
586 int vertexCount() const { return fBatch.fVertexCount; } | |
587 int indexCount() const { return fBatch.fIndexCount; } | |
588 bool coverageIgnored() const { return fBatch.fCoverageIgnored; } | |
589 | |
590 bool onCombineIfPossible(GrBatch* t) override { | |
591 if (!this->pipeline()->isEqual(*t->pipeline())) { | |
592 return false; | |
593 } | |
594 | |
595 DrawVerticesBatch* that = t->cast<DrawVerticesBatch>(); | |
596 | |
597 if (!this->batchablePrimitiveType() || this->primitiveType() != that->pr
imitiveType()) { | |
598 return false; | |
599 } | |
600 | |
601 SkASSERT(this->usesLocalCoords() == that->usesLocalCoords()); | |
602 | |
603 // We currently use a uniform viewmatrix for this batch | |
604 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { | |
605 return false; | |
606 } | |
607 | |
608 if (this->hasColors() != that->hasColors()) { | |
609 return false; | |
610 } | |
611 | |
612 if (this->hasIndices() != that->hasIndices()) { | |
613 return false; | |
614 } | |
615 | |
616 if (this->hasLocalCoords() != that->hasLocalCoords()) { | |
617 return false; | |
618 } | |
619 | |
620 if (!this->hasColors() && this->color() != that->color()) { | |
621 return false; | |
622 } | |
623 | |
624 if (this->color() != that->color()) { | |
625 fBatch.fColor = GrColor_ILLEGAL; | |
626 } | |
627 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin())
; | |
628 fBatch.fVertexCount += that->vertexCount(); | |
629 fBatch.fIndexCount += that->indexCount(); | |
630 | |
631 this->joinBounds(that->bounds()); | |
632 return true; | |
633 } | |
634 | |
635 struct BatchTracker { | |
636 GrPrimitiveType fPrimitiveType; | |
637 SkMatrix fViewMatrix; | |
638 GrColor fColor; | |
639 bool fUsesLocalCoords; | |
640 bool fColorIgnored; | |
641 bool fCoverageIgnored; | |
642 bool fHasColors; | |
643 bool fHasIndices; | |
644 bool fHasLocalCoords; | |
645 int fVertexCount; | |
646 int fIndexCount; | |
647 }; | |
648 | |
649 BatchTracker fBatch; | |
650 SkSTArray<1, Geometry, true> fGeoData; | |
651 }; | |
652 | |
653 void GrDrawContext::drawVertices(GrRenderTarget* rt, | 376 void GrDrawContext::drawVertices(GrRenderTarget* rt, |
654 const GrClip& clip, | 377 const GrClip& clip, |
655 const GrPaint& paint, | 378 const GrPaint& paint, |
656 const SkMatrix& viewMatrix, | 379 const SkMatrix& viewMatrix, |
657 GrPrimitiveType primitiveType, | 380 GrPrimitiveType primitiveType, |
658 int vertexCount, | 381 int vertexCount, |
659 const SkPoint positions[], | 382 const SkPoint positions[], |
660 const SkPoint texCoords[], | 383 const SkPoint texCoords[], |
661 const GrColor colors[], | 384 const GrColor colors[], |
662 const uint16_t indices[], | 385 const uint16_t indices[], |
(...skipping 14 matching lines...) Expand all Loading... |
677 } | 400 } |
678 | 401 |
679 viewMatrix.mapRect(&bounds); | 402 viewMatrix.mapRect(&bounds); |
680 | 403 |
681 // If we don't have AA then we outset for a half pixel in each direction to
account for | 404 // If we don't have AA then we outset for a half pixel in each direction to
account for |
682 // snapping | 405 // snapping |
683 if (!paint.isAntiAlias()) { | 406 if (!paint.isAntiAlias()) { |
684 bounds.outset(0.5f, 0.5f); | 407 bounds.outset(0.5f, 0.5f); |
685 } | 408 } |
686 | 409 |
687 DrawVerticesBatch::Geometry geometry; | 410 GrDrawVerticesBatch::Geometry geometry; |
688 geometry.fColor = paint.getColor(); | 411 geometry.fColor = paint.getColor(); |
689 SkAutoTUnref<GrBatch> batch(DrawVerticesBatch::Create(geometry, primitiveTyp
e, viewMatrix, | 412 SkAutoTUnref<GrBatch> batch(GrDrawVerticesBatch::Create(geometry, primitiveT
ype, viewMatrix, |
690 positions, vertexCount
, indices, | 413 positions, vertexCou
nt, indices, |
691 indexCount, colors, te
xCoords, | 414 indexCount, colors,
texCoords, |
692 bounds)); | 415 bounds)); |
693 | 416 |
694 fDrawTarget->drawBatch(pipelineBuilder, batch); | 417 fDrawTarget->drawBatch(pipelineBuilder, batch); |
695 } | 418 } |
696 | 419 |
697 /////////////////////////////////////////////////////////////////////////////// | 420 /////////////////////////////////////////////////////////////////////////////// |
698 | 421 |
699 void GrDrawContext::drawAtlas(GrRenderTarget* rt, | 422 void GrDrawContext::drawAtlas(GrRenderTarget* rt, |
700 const GrClip& clip, | 423 const GrClip& clip, |
701 const GrPaint& paint, | 424 const GrPaint& paint, |
702 const SkMatrix& viewMatrix, | 425 const SkMatrix& viewMatrix, |
(...skipping 398 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1101 RETURN_FALSE_IF_ABANDONED | 824 RETURN_FALSE_IF_ABANDONED |
1102 | 825 |
1103 ASSERT_OWNED_RESOURCE(rt); | 826 ASSERT_OWNED_RESOURCE(rt); |
1104 SkASSERT(rt); | 827 SkASSERT(rt); |
1105 return true; | 828 return true; |
1106 } | 829 } |
1107 | 830 |
1108 void GrDrawContext::drawBatch(GrPipelineBuilder* pipelineBuilder, GrBatch* batch
) { | 831 void GrDrawContext::drawBatch(GrPipelineBuilder* pipelineBuilder, GrBatch* batch
) { |
1109 fDrawTarget->drawBatch(*pipelineBuilder, batch); | 832 fDrawTarget->drawBatch(*pipelineBuilder, batch); |
1110 } | 833 } |
1111 | |
1112 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
1113 | |
1114 #ifdef GR_TEST_UTILS | |
1115 | |
1116 static uint32_t seed_vertices(GrPrimitiveType type) { | |
1117 switch (type) { | |
1118 case kTriangles_GrPrimitiveType: | |
1119 case kTriangleStrip_GrPrimitiveType: | |
1120 case kTriangleFan_GrPrimitiveType: | |
1121 return 3; | |
1122 case kPoints_GrPrimitiveType: | |
1123 return 1; | |
1124 case kLines_GrPrimitiveType: | |
1125 case kLineStrip_GrPrimitiveType: | |
1126 return 2; | |
1127 } | |
1128 SkFAIL("Incomplete switch\n"); | |
1129 return 0; | |
1130 } | |
1131 | |
1132 static uint32_t primitive_vertices(GrPrimitiveType type) { | |
1133 switch (type) { | |
1134 case kTriangles_GrPrimitiveType: | |
1135 return 3; | |
1136 case kLines_GrPrimitiveType: | |
1137 return 2; | |
1138 case kTriangleStrip_GrPrimitiveType: | |
1139 case kTriangleFan_GrPrimitiveType: | |
1140 case kPoints_GrPrimitiveType: | |
1141 case kLineStrip_GrPrimitiveType: | |
1142 return 1; | |
1143 } | |
1144 SkFAIL("Incomplete switch\n"); | |
1145 return 0; | |
1146 } | |
1147 | |
1148 static SkPoint random_point(SkRandom* random, SkScalar min, SkScalar max) { | |
1149 SkPoint p; | |
1150 p.fX = random->nextRangeScalar(min, max); | |
1151 p.fY = random->nextRangeScalar(min, max); | |
1152 return p; | |
1153 } | |
1154 | |
1155 static void randomize_params(size_t count, size_t maxVertex, SkScalar min, SkSca
lar max, | |
1156 SkRandom* random, | |
1157 SkTArray<SkPoint>* positions, | |
1158 SkTArray<SkPoint>* texCoords, bool hasTexCoords, | |
1159 SkTArray<GrColor>* colors, bool hasColors, | |
1160 SkTArray<uint16_t>* indices, bool hasIndices) { | |
1161 for (uint32_t v = 0; v < count; v++) { | |
1162 positions->push_back(random_point(random, min, max)); | |
1163 if (hasTexCoords) { | |
1164 texCoords->push_back(random_point(random, min, max)); | |
1165 } | |
1166 if (hasColors) { | |
1167 colors->push_back(GrRandomColor(random)); | |
1168 } | |
1169 if (hasIndices) { | |
1170 SkASSERT(maxVertex <= SK_MaxU16); | |
1171 indices->push_back(random->nextULessThan((uint16_t)maxVertex)); | |
1172 } | |
1173 } | |
1174 } | |
1175 | |
1176 BATCH_TEST_DEFINE(VerticesBatch) { | |
1177 GrPrimitiveType type = GrPrimitiveType(random->nextULessThan(kLast_GrPrimiti
veType + 1)); | |
1178 uint32_t primitiveCount = random->nextRangeU(1, 100); | |
1179 | |
1180 // TODO make 'sensible' indexbuffers | |
1181 SkTArray<SkPoint> positions; | |
1182 SkTArray<SkPoint> texCoords; | |
1183 SkTArray<GrColor> colors; | |
1184 SkTArray<uint16_t> indices; | |
1185 | |
1186 bool hasTexCoords = random->nextBool(); | |
1187 bool hasIndices = random->nextBool(); | |
1188 bool hasColors = random->nextBool(); | |
1189 | |
1190 uint32_t vertexCount = seed_vertices(type) + (primitiveCount - 1) * primitiv
e_vertices(type); | |
1191 | |
1192 static const SkScalar kMinVertExtent = -100.f; | |
1193 static const SkScalar kMaxVertExtent = 100.f; | |
1194 randomize_params(seed_vertices(type), vertexCount, kMinVertExtent, kMaxVertE
xtent, | |
1195 random, | |
1196 &positions, | |
1197 &texCoords, hasTexCoords, | |
1198 &colors, hasColors, | |
1199 &indices, hasIndices); | |
1200 | |
1201 for (uint32_t i = 1; i < primitiveCount; i++) { | |
1202 randomize_params(primitive_vertices(type), vertexCount, kMinVertExtent,
kMaxVertExtent, | |
1203 random, | |
1204 &positions, | |
1205 &texCoords, hasTexCoords, | |
1206 &colors, hasColors, | |
1207 &indices, hasIndices); | |
1208 } | |
1209 | |
1210 SkMatrix viewMatrix = GrTest::TestMatrix(random); | |
1211 SkRect bounds; | |
1212 SkDEBUGCODE(bool result = ) bounds.setBoundsCheck(positions.begin(), vertexC
ount); | |
1213 SkASSERT(result); | |
1214 | |
1215 viewMatrix.mapRect(&bounds); | |
1216 | |
1217 DrawVerticesBatch::Geometry geometry; | |
1218 geometry.fColor = GrRandomColor(random); | |
1219 return DrawVerticesBatch::Create(geometry, type, viewMatrix, | |
1220 positions.begin(), vertexCount, | |
1221 indices.begin(), hasIndices ? vertexCount :
0, | |
1222 colors.begin(), | |
1223 texCoords.begin(), | |
1224 bounds); | |
1225 } | |
1226 | |
1227 #endif | |
1228 | |
OLD | NEW |