Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(216)

Side by Side Diff: src/gpu/GrAAConvexPathRenderer.cpp

Issue 880643002: Convex batch (Closed) Base URL: https://skia.googlesource.com/skia.git@hair_defer
Patch Set: more cleanup Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 1
2 /* 2 /*
3 * Copyright 2012 Google Inc. 3 * Copyright 2012 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 "GrAAConvexPathRenderer.h" 9 #include "GrAAConvexPathRenderer.h"
10 10
11 #include "GrBatch.h"
12 #include "GrBatchTarget.h"
13 #include "GrBufferAllocPool.h"
11 #include "GrContext.h" 14 #include "GrContext.h"
12 #include "GrDrawTargetCaps.h" 15 #include "GrDrawTargetCaps.h"
13 #include "GrGeometryProcessor.h" 16 #include "GrGeometryProcessor.h"
14 #include "GrInvariantOutput.h" 17 #include "GrInvariantOutput.h"
15 #include "GrPathUtils.h" 18 #include "GrPathUtils.h"
16 #include "GrProcessor.h" 19 #include "GrProcessor.h"
17 #include "GrPipelineBuilder.h" 20 #include "GrPipelineBuilder.h"
18 #include "SkGeometry.h" 21 #include "SkGeometry.h"
19 #include "SkString.h" 22 #include "SkString.h"
20 #include "SkStrokeRec.h" 23 #include "SkStrokeRec.h"
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 SkASSERT(!m.hasPerspective()); 218 SkASSERT(!m.hasPerspective());
216 SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMS caleY)) - 219 SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMS caleY)) -
217 SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSk ewY)); 220 SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSk ewY));
218 if (det2x2 < 0) { 221 if (det2x2 < 0) {
219 *dir = SkPath::OppositeDirection(*dir); 222 *dir = SkPath::OppositeDirection(*dir);
220 } 223 }
221 return true; 224 return true;
222 } 225 }
223 226
224 static inline void add_line_to_segment(const SkPoint& pt, 227 static inline void add_line_to_segment(const SkPoint& pt,
225 SegmentArray* segments, 228 SegmentArray* segments) {
226 SkRect* devBounds) {
227 segments->push_back(); 229 segments->push_back();
228 segments->back().fType = Segment::kLine; 230 segments->back().fType = Segment::kLine;
229 segments->back().fPts[0] = pt; 231 segments->back().fPts[0] = pt;
230 devBounds->growToInclude(pt.fX, pt.fY);
231 } 232 }
232 233
233 #ifdef SK_DEBUG 234 #ifdef SK_DEBUG
234 static inline bool contains_inclusive(const SkRect& rect, const SkPoint& p) { 235 static inline bool contains_inclusive(const SkRect& rect, const SkPoint& p) {
235 return p.fX >= rect.fLeft && p.fX <= rect.fRight && p.fY >= rect.fTop && p.f Y <= rect.fBottom; 236 return p.fX >= rect.fLeft && p.fX <= rect.fRight && p.fY >= rect.fTop && p.f Y <= rect.fBottom;
236 } 237 }
237 #endif 238 #endif
238 239
239 static inline void add_quad_segment(const SkPoint pts[3], 240 static inline void add_quad_segment(const SkPoint pts[3],
240 SegmentArray* segments, 241 SegmentArray* segments) {
241 SkRect* devBounds) {
242 if (pts[0].distanceToSqd(pts[1]) < kCloseSqd || pts[1].distanceToSqd(pts[2]) < kCloseSqd) { 242 if (pts[0].distanceToSqd(pts[1]) < kCloseSqd || pts[1].distanceToSqd(pts[2]) < kCloseSqd) {
243 if (pts[0] != pts[2]) { 243 if (pts[0] != pts[2]) {
244 add_line_to_segment(pts[2], segments, devBounds); 244 add_line_to_segment(pts[2], segments);
245 } 245 }
246 } else { 246 } else {
247 segments->push_back(); 247 segments->push_back();
248 segments->back().fType = Segment::kQuad; 248 segments->back().fType = Segment::kQuad;
249 segments->back().fPts[0] = pts[1]; 249 segments->back().fPts[0] = pts[1];
250 segments->back().fPts[1] = pts[2]; 250 segments->back().fPts[1] = pts[2];
251 SkASSERT(contains_inclusive(*devBounds, pts[0]));
252 devBounds->growToInclude(pts + 1, 2);
253 } 251 }
254 } 252 }
255 253
256 static inline void add_cubic_segments(const SkPoint pts[4], 254 static inline void add_cubic_segments(const SkPoint pts[4],
257 SkPath::Direction dir, 255 SkPath::Direction dir,
258 SegmentArray* segments, 256 SegmentArray* segments) {
259 SkRect* devBounds) {
260 SkSTArray<15, SkPoint, true> quads; 257 SkSTArray<15, SkPoint, true> quads;
261 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, true, dir, &quads); 258 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, true, dir, &quads);
262 int count = quads.count(); 259 int count = quads.count();
263 for (int q = 0; q < count; q += 3) { 260 for (int q = 0; q < count; q += 3) {
264 add_quad_segment(&quads[q], segments, devBounds); 261 add_quad_segment(&quads[q], segments);
265 } 262 }
266 } 263 }
267 264
268 static bool get_segments(const SkPath& path, 265 static bool get_segments(const SkPath& path,
269 const SkMatrix& m, 266 const SkMatrix& m,
270 SegmentArray* segments, 267 SegmentArray* segments,
271 SkPoint* fanPt, 268 SkPoint* fanPt,
272 int* vCount, 269 int* vCount,
273 int* iCount, 270 int* iCount) {
274 SkRect* devBounds) {
275 SkPath::Iter iter(path, true); 271 SkPath::Iter iter(path, true);
276 // This renderer over-emphasizes very thin path regions. We use the distance 272 // This renderer over-emphasizes very thin path regions. We use the distance
277 // to the path from the sample to compute coverage. Every pixel intersected 273 // to the path from the sample to compute coverage. Every pixel intersected
278 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't 274 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
279 // notice that the sample may be close to a very thin area of the path and 275 // notice that the sample may be close to a very thin area of the path and
280 // thus should be very light. This is particularly egregious for degenerate 276 // thus should be very light. This is particularly egregious for degenerate
281 // line paths. We detect paths that are very close to a line (zero area) and 277 // line paths. We detect paths that are very close to a line (zero area) and
282 // draw nothing. 278 // draw nothing.
283 DegenerateTestData degenerateData; 279 DegenerateTestData degenerateData;
284 SkPath::Direction dir; 280 SkPath::Direction dir;
285 // get_direction can fail for some degenerate paths. 281 // get_direction can fail for some degenerate paths.
286 if (!get_direction(path, m, &dir)) { 282 if (!get_direction(path, m, &dir)) {
287 return false; 283 return false;
288 } 284 }
289 285
290 for (;;) { 286 for (;;) {
291 SkPoint pts[4]; 287 SkPoint pts[4];
292 SkPath::Verb verb = iter.next(pts); 288 SkPath::Verb verb = iter.next(pts);
293 switch (verb) { 289 switch (verb) {
294 case SkPath::kMove_Verb: 290 case SkPath::kMove_Verb:
295 m.mapPoints(pts, 1); 291 m.mapPoints(pts, 1);
296 update_degenerate_test(&degenerateData, pts[0]); 292 update_degenerate_test(&degenerateData, pts[0]);
297 devBounds->set(pts->fX, pts->fY, pts->fX, pts->fY);
298 break; 293 break;
299 case SkPath::kLine_Verb: { 294 case SkPath::kLine_Verb: {
300 m.mapPoints(&pts[1], 1); 295 m.mapPoints(&pts[1], 1);
301 update_degenerate_test(&degenerateData, pts[1]); 296 update_degenerate_test(&degenerateData, pts[1]);
302 add_line_to_segment(pts[1], segments, devBounds); 297 add_line_to_segment(pts[1], segments);
303 break; 298 break;
304 } 299 }
305 case SkPath::kQuad_Verb: 300 case SkPath::kQuad_Verb:
306 m.mapPoints(pts, 3); 301 m.mapPoints(pts, 3);
307 update_degenerate_test(&degenerateData, pts[1]); 302 update_degenerate_test(&degenerateData, pts[1]);
308 update_degenerate_test(&degenerateData, pts[2]); 303 update_degenerate_test(&degenerateData, pts[2]);
309 add_quad_segment(pts, segments, devBounds); 304 add_quad_segment(pts, segments);
310 break; 305 break;
311 case SkPath::kConic_Verb: { 306 case SkPath::kConic_Verb: {
312 m.mapPoints(pts, 3); 307 m.mapPoints(pts, 3);
313 SkScalar weight = iter.conicWeight(); 308 SkScalar weight = iter.conicWeight();
314 SkAutoConicToQuads converter; 309 SkAutoConicToQuads converter;
315 const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.5 f); 310 const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.5 f);
316 for (int i = 0; i < converter.countQuads(); ++i) { 311 for (int i = 0; i < converter.countQuads(); ++i) {
317 update_degenerate_test(&degenerateData, quadPts[2*i + 1]); 312 update_degenerate_test(&degenerateData, quadPts[2*i + 1]);
318 update_degenerate_test(&degenerateData, quadPts[2*i + 2]); 313 update_degenerate_test(&degenerateData, quadPts[2*i + 2]);
319 add_quad_segment(quadPts + 2*i, segments, devBounds); 314 add_quad_segment(quadPts + 2*i, segments);
320 } 315 }
321 break; 316 break;
322 } 317 }
323 case SkPath::kCubic_Verb: { 318 case SkPath::kCubic_Verb: {
324 m.mapPoints(pts, 4); 319 m.mapPoints(pts, 4);
325 update_degenerate_test(&degenerateData, pts[1]); 320 update_degenerate_test(&degenerateData, pts[1]);
326 update_degenerate_test(&degenerateData, pts[2]); 321 update_degenerate_test(&degenerateData, pts[2]);
327 update_degenerate_test(&degenerateData, pts[3]); 322 update_degenerate_test(&degenerateData, pts[3]);
328 add_cubic_segments(pts, dir, segments, devBounds); 323 add_cubic_segments(pts, dir, segments);
329 break; 324 break;
330 }; 325 };
331 case SkPath::kDone_Verb: 326 case SkPath::kDone_Verb:
332 if (degenerateData.isDegenerate()) { 327 if (degenerateData.isDegenerate()) {
333 return false; 328 return false;
334 } else { 329 } else {
335 compute_vectors(segments, fanPt, dir, vCount, iCount); 330 compute_vectors(segments, fanPt, dir, vCount, iCount);
336 return true; 331 return true;
337 } 332 }
338 default: 333 default:
(...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after
696 bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget* target, 691 bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget* target,
697 const GrPipelineBuilder*, 692 const GrPipelineBuilder*,
698 const SkMatrix& viewMatrix, 693 const SkMatrix& viewMatrix,
699 const SkPath& path, 694 const SkPath& path,
700 const SkStrokeRec& stroke, 695 const SkStrokeRec& stroke,
701 bool antiAlias) const { 696 bool antiAlias) const {
702 return (target->caps()->shaderDerivativeSupport() && antiAlias && 697 return (target->caps()->shaderDerivativeSupport() && antiAlias &&
703 stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex() ); 698 stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex() );
704 } 699 }
705 700
701 class AAConvexPathBatch : public GrBatch {
702 public:
703 struct Geometry {
704 GrColor fColor;
705 SkMatrix fViewMatrix;
706 SkPath fPath;
707 SkDEBUGCODE(SkRect fDevBounds;)
708 };
709
710 static GrBatch* Create(const Geometry& geometry) {
711 return SkNEW_ARGS(AAConvexPathBatch, (geometry));
712 }
713
714 const char* name() const SK_OVERRIDE { return "AAConvexBatch"; }
715
716 void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
717 // When this is called on a batch, there is only one geometry bundle
718 out->setKnownFourComponents(fGeoData[0].fColor);
719 }
720 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRID E {
721 out->setUnknownSingleComponent();
722 }
723
724 void initBatchOpt(const GrBatchOpt& batchOpt) {
725 fBatchOpt = batchOpt;
726 }
727
728 void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
729 // Handle any color overrides
730 if (init.fColorIgnored) {
731 fGeoData[0].fColor = GrColor_ILLEGAL;
732 } else if (GrColor_ILLEGAL != init.fOverrideColor) {
733 fGeoData[0].fColor = init.fOverrideColor;
734 }
735
736 // setup batch properties
737 fBatch.fColorIgnored = init.fColorIgnored;
738 fBatch.fColor = fGeoData[0].fColor;
739 fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
740 fBatch.fCoverageIgnored = init.fCoverageIgnored;
741 }
742
743 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline ) SK_OVERRIDE {
744 int instanceCount = fGeoData.count();
745 for (int i = 0; i < instanceCount; i++) {
746 Geometry& args = fGeoData[i];
747
748 const SkMatrix* viewMatrix = &args.fViewMatrix;
749 SkMatrix invert;
750 if (!viewMatrix->invert(&invert)) {
751 continue;
752 }
753
754 // We use the fact that SkPath::transform path does subdivision base d on
755 // perspective. Otherwise, we apply the view matrix when copying to the
756 // segment representation.
757 if (viewMatrix->hasPerspective()) {
758 args.fPath.transform(*viewMatrix);
759 viewMatrix = &SkMatrix::I();
760 }
761
762 int vertexCount;
763 int indexCount;
764 enum {
765 kPreallocSegmentCnt = 512 / sizeof(Segment),
766 kPreallocDrawCnt = 4,
767 };
768 SkSTArray<kPreallocSegmentCnt, Segment, true> segments;
769 SkPoint fanPt;
770
771 if (!get_segments(args.fPath, *viewMatrix, &segments, &fanPt, &verte xCount,
772 &indexCount)) {
773 continue;
774 }
775
776 SkAutoTUnref<GrGeometryProcessor> quadProcessor(QuadEdgeEffect::Crea te(args.fColor,
777 invert));
778
779 batchTarget->initDraw(quadProcessor, pipeline);
780 fBatchesGenerated++;
781
782 // TODO remove this when batch is everywhere
783 GrPipelineInfo init;
784 init.fColorIgnored = fBatch.fColorIgnored;
785 init.fOverrideColor = GrColor_ILLEGAL;
786 init.fCoverageIgnored = fBatch.fCoverageIgnored;
787 init.fUsesLocalCoords = this->usesLocalCoords();
788 quadProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init);
789
790 const GrVertexBuffer* vertexBuffer;
791 int firstVertex;
792
793 size_t vertexStride = quadProcessor->getVertexStride();
794 void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
795 vertexCount,
796 &vertexBuffer,
797 &firstVertex);
798
799 const GrIndexBuffer* indexBuffer;
800 int firstIndex;
801
802 void *indices = batchTarget->indexPool()->makeSpace(indexCount,
803 &indexBuffer,
804 &firstIndex);
805
806 QuadVertex* verts = reinterpret_cast<QuadVertex*>(vertices);
807 uint16_t* idxs = reinterpret_cast<uint16_t*>(indices);
808
809 SkSTArray<kPreallocDrawCnt, Draw, true> draws;
810 create_vertices(segments, fanPt, &draws, verts, idxs);
811
812 #ifdef SK_DEBUG
813 // Check devBounds
814 SkRect tolDevBounds = args.fDevBounds;
815 tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
816 SkRect actualBounds;
817 actualBounds.set(verts[0].fPos, verts[1].fPos);
818 for (int i = 2; i < vertexCount; ++i) {
819 actualBounds.growToInclude(verts[i].fPos.fX, verts[i].fPos.fY);
820 }
821 SkASSERT(tolDevBounds.contains(actualBounds));
822 #endif
823
824 GrDrawTarget::DrawInfo info;
825 info.setVertexBuffer(vertexBuffer);
826 info.setIndexBuffer(indexBuffer);
827 info.setPrimitiveType(kTriangles_GrPrimitiveType);
828 info.setStartIndex(firstIndex);
829
830 int vOffset = 0;
831 for (int i = 0; i < draws.count(); ++i) {
832 const Draw& draw = draws[i];
833 info.setStartVertex(vOffset + firstVertex);
834 info.setVertexCount(draw.fVertexCnt);
835 info.setIndexCount(draw.fIndexCnt);
836 batchTarget->draw(info);
837 vOffset += draw.fVertexCnt;
838 }
839 }
840 }
841
842 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
843
844 private:
845 AAConvexPathBatch(const Geometry& geometry) {
846 this->initClassID<AAConvexPathBatch>();
847 fGeoData.push_back(geometry);
848 }
849
850 bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
851 AAConvexPathBatch* that = t->cast<AAConvexPathBatch>();
852
853 // TODO we can 'batch' with color because we draw each convex path separ ately
854 if (this->color() != that->color()) {
855 return false;
856 }
857
858 SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
859 if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->vi ewMatrix())) {
860 return false;
861 }
862
863 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin()) ;
864 return true;
865 }
866
867 GrColor color() const { return fBatch.fColor; }
868 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
869 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
870
871 struct BatchTracker {
872 GrColor fColor;
873 bool fUsesLocalCoords;
874 bool fColorIgnored;
875 bool fCoverageIgnored;
876 };
877
878 GrBatchOpt fBatchOpt;
879 BatchTracker fBatch;
880 SkSTArray<1, Geometry, true> fGeoData;
881 };
882
706 bool GrAAConvexPathRenderer::onDrawPath(GrDrawTarget* target, 883 bool GrAAConvexPathRenderer::onDrawPath(GrDrawTarget* target,
707 GrPipelineBuilder* pipelineBuilder, 884 GrPipelineBuilder* pipelineBuilder,
708 GrColor color, 885 GrColor color,
709 const SkMatrix& vm, 886 const SkMatrix& vm,
710 const SkPath& origPath, 887 const SkPath& path,
711 const SkStrokeRec&, 888 const SkStrokeRec&,
712 bool antiAlias) { 889 bool antiAlias) {
713 890 if (path.isEmpty()) {
714 const SkPath* path = &origPath;
715 if (path->isEmpty()) {
716 return true; 891 return true;
717 } 892 }
718 893
719 SkMatrix viewMatrix = vm; 894 // This outset was determined experimentally by running skps and gms. It pr obably could be a
bsalomon 2015/02/03 18:09:41 yikes!!! That seems really dangerous. I think we n
720 SkMatrix invert; 895 // bit tighter
721 if (!viewMatrix.invert(&invert)) { 896 SkRect devRect = path.getBounds();
722 return false; 897 devRect.outset(7, 7);
723 } 898 vm.mapRect(&devRect);
724 899
725 // We use the fact that SkPath::transform path does subdivision based on 900 AAConvexPathBatch::Geometry geometry;
726 // perspective. Otherwise, we apply the view matrix when copying to the 901 geometry.fColor = color;
727 // segment representation. 902 geometry.fViewMatrix = vm;
728 SkPath tmpPath; 903 geometry.fPath = path;
729 if (viewMatrix.hasPerspective()) { 904 SkDEBUGCODE(geometry.fDevBounds = devRect;)
730 origPath.transform(viewMatrix, &tmpPath); 905
731 path = &tmpPath; 906 GrBatch* batch = AAConvexPathBatch::Create(geometry);
732 viewMatrix = SkMatrix::I(); 907 target->drawBatch(pipelineBuilder, batch, &devRect);
733 }
734
735 QuadVertex *verts;
736 uint16_t* idxs;
737
738 int vCount;
739 int iCount;
740 enum {
741 kPreallocSegmentCnt = 512 / sizeof(Segment),
742 kPreallocDrawCnt = 4,
743 };
744 SkSTArray<kPreallocSegmentCnt, Segment, true> segments;
745 SkPoint fanPt;
746
747 // We can't simply use the path bounds because we may degenerate cubics to q uads which produces
748 // new control points outside the original convex hull.
749 SkRect devBounds;
750 if (!get_segments(*path, viewMatrix, &segments, &fanPt, &vCount, &iCount, &d evBounds)) {
751 return false;
752 }
753
754 // Our computed verts should all be within one pixel of the segment control points.
755 devBounds.outset(SK_Scalar1, SK_Scalar1);
756
757 SkAutoTUnref<GrGeometryProcessor> quadProcessor(QuadEdgeEffect::Create(color , invert));
758
759 GrDrawTarget::AutoReleaseGeometry arg(target, vCount, quadProcessor->getVert exStride(), iCount);
760 SkASSERT(quadProcessor->getVertexStride() == sizeof(QuadVertex));
761 if (!arg.succeeded()) {
762 return false;
763 }
764 verts = reinterpret_cast<QuadVertex*>(arg.vertices());
765 idxs = reinterpret_cast<uint16_t*>(arg.indices());
766
767 SkSTArray<kPreallocDrawCnt, Draw, true> draws;
768 create_vertices(segments, fanPt, &draws, verts, idxs);
769
770 // Check devBounds
771 #ifdef SK_DEBUG
772 SkRect tolDevBounds = devBounds;
773 tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
774 SkRect actualBounds;
775 actualBounds.set(verts[0].fPos, verts[1].fPos);
776 for (int i = 2; i < vCount; ++i) {
777 actualBounds.growToInclude(verts[i].fPos.fX, verts[i].fPos.fY);
778 }
779 SkASSERT(tolDevBounds.contains(actualBounds));
780 #endif
781
782 int vOffset = 0;
783 for (int i = 0; i < draws.count(); ++i) {
784 const Draw& draw = draws[i];
785 target->drawIndexed(pipelineBuilder,
786 quadProcessor,
787 kTriangles_GrPrimitiveType,
788 vOffset, // start vertex
789 0, // start index
790 draw.fVertexCnt,
791 draw.fIndexCnt,
792 &devBounds);
793 vOffset += draw.fVertexCnt;
794 }
795 908
796 return true; 909 return true;
910
797 } 911 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698