blob: 5e6b824c9bf81d28ebf3ea9151617ff574b770b2 [file] [log] [blame]
reed@google.com873cb1e2010-12-23 15:00:45 +00001/*
2 Copyright 2010 Google Inc.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
reed@google.comac10a2d2010-12-22 21:39:39 +000017
18#include "GrContext.h"
19#include "GrTextureCache.h"
20#include "GrTextStrike.h"
21#include "GrMemory.h"
22#include "GrPathIter.h"
23#include "GrClipIterator.h"
24#include "GrIndexBuffer.h"
25
26#define DEFER_TEXT_RENDERING 1
27
28static const size_t MAX_TEXTURE_CACHE_COUNT = 128;
29static const size_t MAX_TEXTURE_CACHE_BYTES = 8 * 1024 * 1024;
30
31#if DEFER_TEXT_RENDERING
32 static const uint32_t POOL_VB_SIZE = 2048 *
bsalomon@google.com2e7b43d2011-01-18 20:57:22 +000033 GrDrawTarget::VertexSize(
34 GrDrawTarget::kTextFormat_VertexLayoutBit |
bsalomon@google.com1572b072011-01-18 15:30:57 +000035 GrDrawTarget::StageTexCoordVertexLayoutBit(0,0));
reed@google.comac10a2d2010-12-22 21:39:39 +000036 static const uint32_t NUM_POOL_VBS = 8;
37#else
38 static const uint32_t POOL_VB_SIZE = 0;
39 static const uint32_t NUM_POOL_VBS = 0;
40
41#endif
42
43GrContext* GrContext::Create(GrGpu::Engine engine,
44 GrGpu::Platform3DContext context3D) {
45 GrContext* ctx = NULL;
46 GrGpu* fGpu = GrGpu::Create(engine, context3D);
47 if (NULL != fGpu) {
48 ctx = new GrContext(fGpu);
49 fGpu->unref();
50 }
51 return ctx;
52}
53
reed@google.com873cb1e2010-12-23 15:00:45 +000054GrContext* GrContext::CreateGLShaderContext() {
55 return GrContext::Create(GrGpu::kOpenGL_Shaders_Engine, NULL);
56}
57
reed@google.comac10a2d2010-12-22 21:39:39 +000058GrContext::~GrContext() {
59 fGpu->unref();
60 delete fTextureCache;
61 delete fFontCache;
62}
63
64void GrContext::abandonAllTextures() {
65 fTextureCache->deleteAll(GrTextureCache::kAbandonTexture_DeleteMode);
66 fFontCache->abandonAll();
67}
68
69GrTextureEntry* GrContext::findAndLockTexture(GrTextureKey* key,
70 const GrSamplerState& sampler) {
71 finalizeTextureKey(key, sampler);
72 return fTextureCache->findAndLock(*key);
73}
74
75static void stretchImage(void* dst,
76 int dstW,
77 int dstH,
78 void* src,
79 int srcW,
80 int srcH,
81 int bpp) {
82 GrFixed dx = (srcW << 16) / dstW;
83 GrFixed dy = (srcH << 16) / dstH;
84
85 GrFixed y = dy >> 1;
86
87 int dstXLimit = dstW*bpp;
88 for (int j = 0; j < dstH; ++j) {
89 GrFixed x = dx >> 1;
90 void* srcRow = (uint8_t*)src + (y>>16)*srcW*bpp;
91 void* dstRow = (uint8_t*)dst + j*dstW*bpp;
92 for (int i = 0; i < dstXLimit; i += bpp) {
93 memcpy((uint8_t*) dstRow + i,
94 (uint8_t*) srcRow + (x>>16)*bpp,
95 bpp);
96 x += dx;
97 }
98 y += dy;
99 }
100}
101
102GrTextureEntry* GrContext::createAndLockTexture(GrTextureKey* key,
103 const GrSamplerState& sampler,
104 const GrGpu::TextureDesc& desc,
105 void* srcData, size_t rowBytes) {
106 GrAssert(key->width() == desc.fWidth);
107 GrAssert(key->height() == desc.fHeight);
108
109#if GR_DUMP_TEXTURE_UPLOAD
110 GrPrintf("GrContext::createAndLockTexture [%d %d]\n", desc.fWidth, desc.fHeight);
111#endif
112
113 GrTextureEntry* entry = NULL;
114 bool special = finalizeTextureKey(key, sampler);
115 if (special) {
116 GrTextureEntry* clampEntry;
117 GrTextureKey clampKey(*key);
118 clampEntry = findAndLockTexture(&clampKey, GrSamplerState::ClampNoFilter());
119
120 if (NULL == clampEntry) {
121 clampEntry = createAndLockTexture(&clampKey,
122 GrSamplerState::ClampNoFilter(),
123 desc, srcData, rowBytes);
124 GrAssert(NULL != clampEntry);
125 if (NULL == clampEntry) {
126 return NULL;
127 }
128 }
129 GrTexture* clampTexture = clampEntry->texture();
130 GrGpu::TextureDesc rtDesc = desc;
131 rtDesc.fFlags |= GrGpu::kRenderTarget_TextureFlag |
132 GrGpu::kNoPathRendering_TextureFlag;
133 rtDesc.fWidth = GrNextPow2(GrMax<int>(desc.fWidth,
134 fGpu->minRenderTargetWidth()));
135 rtDesc.fHeight = GrNextPow2(GrMax<int>(desc.fHeight,
136 fGpu->minRenderTargetHeight()));
137
138 GrTexture* texture = fGpu->createTexture(rtDesc, NULL, 0);
139
140 if (NULL != texture) {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000141 GrDrawTarget::AutoStateRestore asr(fGpu);
reed@google.comac10a2d2010-12-22 21:39:39 +0000142 fGpu->setRenderTarget(texture->asRenderTarget());
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000143 fGpu->setTexture(0, clampEntry->texture());
bsalomon@google.com5782d712011-01-21 21:03:59 +0000144 fGpu->setStencilPass(GrDrawTarget::kNone_StencilPass);
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000145 fGpu->setTextureMatrix(0, GrMatrix::I());
reed@google.comac10a2d2010-12-22 21:39:39 +0000146 fGpu->setViewMatrix(GrMatrix::I());
147 fGpu->setAlpha(0xff);
bsalomon@google.com5782d712011-01-21 21:03:59 +0000148 fGpu->setBlendFunc(GrDrawTarget::kOne_BlendCoeff, GrDrawTarget::kZero_BlendCoeff);
149 fGpu->disableState(GrDrawTarget::kDither_StateBit |
150 GrDrawTarget::kClip_StateBit |
151 GrDrawTarget::kAntialias_StateBit);
reed@google.comac10a2d2010-12-22 21:39:39 +0000152 GrSamplerState stretchSampler(GrSamplerState::kClamp_WrapMode,
153 GrSamplerState::kClamp_WrapMode,
154 sampler.isFilter());
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000155 fGpu->setSamplerState(0, stretchSampler);
reed@google.comac10a2d2010-12-22 21:39:39 +0000156
157 static const GrVertexLayout layout =
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000158 GrDrawTarget::StageTexCoordVertexLayoutBit(0,0);
reed@google.comac10a2d2010-12-22 21:39:39 +0000159 GrDrawTarget::AutoReleaseGeometry arg(fGpu, layout, 4, 0);
160
161 if (arg.succeeded()) {
162 GrPoint* verts = (GrPoint*) arg.vertices();
163 verts[0].setIRectFan(0, 0,
164 texture->contentWidth(),
165 texture->contentHeight(),
166 2*sizeof(GrPoint));
167 GrScalar tw = GrFixedToScalar(GR_Fixed1 *
168 clampTexture->contentWidth() /
169 clampTexture->allocWidth());
170 GrScalar th = GrFixedToScalar(GR_Fixed1 *
171 clampTexture->contentHeight() /
172 clampTexture->allocHeight());
173 verts[1].setRectFan(0, 0, tw, th, 2*sizeof(GrPoint));
bsalomon@google.com5782d712011-01-21 21:03:59 +0000174 fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType,
reed@google.comac10a2d2010-12-22 21:39:39 +0000175 0, 4);
176 entry = fTextureCache->createAndLock(*key, texture);
177 }
178 texture->removeRenderTarget();
179 } else {
180 // TODO: Our CPU stretch doesn't filter. But we create separate
181 // stretched textures when the sampler state is either filtered or
182 // not. Either implement filtered stretch blit on CPU or just create
183 // one when FBO case fails.
184
185 rtDesc.fFlags = 0;
186 // no longer need to clamp at min RT size.
187 rtDesc.fWidth = GrNextPow2(desc.fWidth);
188 rtDesc.fHeight = GrNextPow2(desc.fHeight);
189 int bpp = GrTexture::BytesPerPixel(desc.fFormat);
190 GrAutoSMalloc<128*128*4> stretchedPixels(bpp *
191 rtDesc.fWidth *
192 rtDesc.fHeight);
193 stretchImage(stretchedPixels.get(), rtDesc.fWidth, rtDesc.fHeight,
194 srcData, desc.fWidth, desc.fHeight, bpp);
195
196 size_t stretchedRowBytes = rtDesc.fWidth * bpp;
197
198 GrTexture* texture = fGpu->createTexture(rtDesc,
199 stretchedPixels.get(),
200 stretchedRowBytes);
201 GrAssert(NULL != texture);
202 entry = fTextureCache->createAndLock(*key, texture);
203 }
204 fTextureCache->unlock(clampEntry);
205
206 } else {
207 GrTexture* texture = fGpu->createTexture(desc, srcData, rowBytes);
208 if (NULL != texture) {
209 entry = fTextureCache->createAndLock(*key, texture);
210 } else {
211 entry = NULL;
212 }
213 }
214 return entry;
215}
216
217void GrContext::unlockTexture(GrTextureEntry* entry) {
218 fTextureCache->unlock(entry);
219}
220
221void GrContext::detachCachedTexture(GrTextureEntry* entry) {
222 fTextureCache->detach(entry);
223}
224
225void GrContext::reattachAndUnlockCachedTexture(GrTextureEntry* entry) {
226 fTextureCache->reattachAndUnlock(entry);
227}
228
229GrTexture* GrContext::createUncachedTexture(const GrGpu::TextureDesc& desc,
230 void* srcData,
231 size_t rowBytes) {
232 return fGpu->createTexture(desc, srcData, rowBytes);
233}
234
reed@google.com01804b42011-01-18 21:50:41 +0000235void GrContext::getTextureCacheLimits(int* maxTextures,
236 size_t* maxTextureBytes) const {
237 fTextureCache->getLimits(maxTextures, maxTextureBytes);
238}
239
240void GrContext::setTextureCacheLimits(int maxTextures, size_t maxTextureBytes) {
241 fTextureCache->setLimits(maxTextures, maxTextureBytes);
242}
243
244///////////////////////////////////////////////////////////////////////////////
245
reed@google.comac10a2d2010-12-22 21:39:39 +0000246GrRenderTarget* GrContext::createPlatformRenderTarget(intptr_t platformRenderTarget,
247 int width, int height) {
248 return fGpu->createPlatformRenderTarget(platformRenderTarget,
249 width, height);
250}
251
252bool GrContext::supportsIndex8PixelConfig(const GrSamplerState& sampler,
253 int width, int height) {
254 if (!fGpu->supports8BitPalette()) {
255 return false;
256 }
257
258 bool needsRepeat = sampler.getWrapX() != GrSamplerState::kClamp_WrapMode ||
259 sampler.getWrapY() != GrSamplerState::kClamp_WrapMode;
260 bool isPow2 = GrIsPow2(width) && GrIsPow2(height);
261
262 switch (fGpu->npotTextureSupport()) {
263 case GrGpu::kNone_NPOTTextureType:
264 return isPow2;
265 case GrGpu::kNoRepeat_NPOTTextureType:
266 return isPow2 || !needsRepeat;
267 case GrGpu::kNonRendertarget_NPOTTextureType:
268 case GrGpu::kFull_NPOTTextureType:
269 return true;
270 }
271 // should never get here
272 GrAssert(!"Bad enum from fGpu->npotTextureSupport");
273 return false;
274}
275
276////////////////////////////////////////////////////////////////////////////////
277
bsalomon@google.com5782d712011-01-21 21:03:59 +0000278void GrContext::setClip(const GrClip& clip) {
279 fGpu->setClip(clip);
280 fGpu->enableState(GrDrawTarget::kClip_StateBit);
281}
282
283void GrContext::setClip(const GrIRect& rect) {
284 GrClip clip;
285 clip.setRect(rect);
286 fGpu->setClip(clip);
287}
288
289////////////////////////////////////////////////////////////////////////////////
290
reed@google.comac10a2d2010-12-22 21:39:39 +0000291void GrContext::eraseColor(GrColor color) {
292 fGpu->eraseColor(color);
293}
294
bsalomon@google.com5782d712011-01-21 21:03:59 +0000295void GrContext::drawPaint(const GrPaint& paint) {
reed@google.comac10a2d2010-12-22 21:39:39 +0000296 // set rect to be big enough to fill the space, but not super-huge, so we
297 // don't overflow fixed-point implementations
298 GrRect r(fGpu->getClip().getBounds());
299 GrMatrix inverse;
300 if (fGpu->getViewInverse(&inverse)) {
301 inverse.mapRect(&r);
302 } else {
303 GrPrintf("---- fGpu->getViewInverse failed\n");
304 }
bsalomon@google.com5782d712011-01-21 21:03:59 +0000305 this->drawRect(paint, r);
reed@google.comac10a2d2010-12-22 21:39:39 +0000306}
307
308/* create a triangle strip that strokes the specified triangle. There are 8
309 unique vertices, but we repreat the last 2 to close up. Alternatively we
310 could use an indices array, and then only send 8 verts, but not sure that
311 would be faster.
312 */
313static void setStrokeRectStrip(GrPoint verts[10], const GrRect& rect,
314 GrScalar width) {
315 const GrScalar rad = GrScalarHalf(width);
316
317 verts[0].set(rect.fLeft + rad, rect.fTop + rad);
318 verts[1].set(rect.fLeft - rad, rect.fTop - rad);
319 verts[2].set(rect.fRight - rad, rect.fTop + rad);
320 verts[3].set(rect.fRight + rad, rect.fTop - rad);
321 verts[4].set(rect.fRight - rad, rect.fBottom - rad);
322 verts[5].set(rect.fRight + rad, rect.fBottom + rad);
323 verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
324 verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
325 verts[8] = verts[0];
326 verts[9] = verts[1];
327}
328
bsalomon@google.com5782d712011-01-21 21:03:59 +0000329void GrContext::drawRect(const GrPaint& paint,
330 const GrRect& rect,
331 GrScalar width) {
332
333 GrVertexLayout layout = (NULL != paint.getTexture()) ?
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000334 GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0) :
reed@google.comac10a2d2010-12-22 21:39:39 +0000335 0;
336
337 static const int worstCaseVertCount = 10;
338 GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, worstCaseVertCount, 0);
339 if (!geo.succeeded()) {
340 return;
341 }
342
bsalomon@google.com5782d712011-01-21 21:03:59 +0000343 this->prepareToDraw(paint);
reed@google.comac10a2d2010-12-22 21:39:39 +0000344
345 int vertCount;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000346 GrDrawTarget::PrimitiveType primType;
reed@google.comac10a2d2010-12-22 21:39:39 +0000347 GrPoint* vertex = geo.positions();
348
349 if (width >= 0) {
350 if (width > 0) {
351 vertCount = 10;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000352 primType = GrDrawTarget::kTriangleStrip_PrimitiveType;
reed@google.comac10a2d2010-12-22 21:39:39 +0000353 setStrokeRectStrip(vertex, rect, width);
354 } else {
355 // hairline
356 vertCount = 5;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000357 primType = GrDrawTarget::kLineStrip_PrimitiveType;
reed@google.comac10a2d2010-12-22 21:39:39 +0000358 vertex[0].set(rect.fLeft, rect.fTop);
359 vertex[1].set(rect.fRight, rect.fTop);
360 vertex[2].set(rect.fRight, rect.fBottom);
361 vertex[3].set(rect.fLeft, rect.fBottom);
362 vertex[4].set(rect.fLeft, rect.fTop);
363 }
364 } else {
365 vertCount = 4;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000366 primType = GrDrawTarget::kTriangleFan_PrimitiveType;
reed@google.comac10a2d2010-12-22 21:39:39 +0000367 vertex->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
368 }
369
370 fGpu->drawNonIndexed(primType, 0, vertCount);
371}
372
bsalomon@google.com5782d712011-01-21 21:03:59 +0000373void GrContext::drawRectToRect(const GrPaint& paint,
374 const GrRect& dstRect,
375 const GrRect& srcRect) {
376
377 if (NULL == paint.getTexture()) {
378 drawRect(paint, dstRect);
379 return;
380 }
381
382 GrVertexLayout layout = GrDrawTarget::StageTexCoordVertexLayoutBit(0,0);
383 static const int VCOUNT = 4;
384
385 GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, VCOUNT, 0);
386 if (!geo.succeeded()) {
387 return;
388 }
389
390 this->prepareToDraw(paint);
391
392 GrPoint* vertex = (GrPoint*) geo.vertices();
393
394 vertex[0].setRectFan(dstRect.fLeft, dstRect.fTop,
395 dstRect.fRight, dstRect.fBottom,
396 2 * sizeof(GrPoint));
397 vertex[1].setRectFan(srcRect.fLeft, srcRect.fTop,
398 srcRect.fRight, srcRect.fBottom,
399 2 * sizeof(GrPoint));
400
401 fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, 0, VCOUNT);
402}
403
404void GrContext::drawVertices(const GrPaint& paint,
405 GrDrawTarget::PrimitiveType primitiveType,
406 int vertexCount,
407 const GrPoint positions[],
408 const GrPoint texCoords[],
409 const GrColor colors[],
410 const uint16_t indices[],
411 int indexCount) {
412 GrVertexLayout layout = 0;
413 bool interLeave = false;
414
415 GrDrawTarget::AutoReleaseGeometry geo;
416
417 this->prepareToDraw(paint);
418
419 if (NULL != paint.getTexture()) {
420 if (NULL == texCoords) {
421 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0);
422 } else {
423 layout |= GrDrawTarget::StageTexCoordVertexLayoutBit(0,0);
424 interLeave = true;
425 }
426 }
427
428 if (NULL != colors) {
429 layout |= GrDrawTarget::kColor_VertexLayoutBit;
430 }
431
432 static const GrVertexLayout interleaveMask =
433 (GrDrawTarget::StageTexCoordVertexLayoutBit(0,0) |
434 GrDrawTarget::kColor_VertexLayoutBit);
435 if (interleaveMask & layout) {
436 if (!geo.set(fGpu, layout, vertexCount, 0)) {
437 GrPrintf("Failed to get space for vertices!");
438 return;
439 }
440 int texOffsets[GrDrawTarget::kMaxTexCoords];
441 int colorOffset;
442 int vsize = GrDrawTarget::VertexSizeAndOffsetsByIdx(layout,
443 texOffsets,
444 &colorOffset);
445 void* curVertex = geo.vertices();
446
447 for (int i = 0; i < vertexCount; ++i) {
448 *((GrPoint*)curVertex) = positions[i];
449
450 if (texOffsets[0] > 0) {
451 *(GrPoint*)((intptr_t)curVertex + texOffsets[0]) = texCoords[i];
452 }
453 if (colorOffset > 0) {
454 *(GrColor*)((intptr_t)curVertex + colorOffset) = colors[i];
455 }
456 curVertex = (void*)((intptr_t)curVertex + vsize);
457 }
458 } else {
459 fGpu->setVertexSourceToArray(positions, layout);
460 }
461
462 if (NULL != indices) {
463 fGpu->setIndexSourceToArray(indices);
464 fGpu->drawIndexed(primitiveType, 0, 0, vertexCount, indexCount);
465 } else {
466 fGpu->drawNonIndexed(primitiveType, 0, vertexCount);
467 }
468}
469
470
reed@google.comac10a2d2010-12-22 21:39:39 +0000471////////////////////////////////////////////////////////////////////////////////
472
473#define NEW_EVAL 1 // Use adaptive path tesselation
474#define STENCIL_OFF 0 // Always disable stencil (even when needed)
475#define CPU_TRANSFORM 0 // Transform path verts on CPU
476
477#if NEW_EVAL
478
479#define EVAL_TOL GR_Scalar1
480
481static uint32_t quadratic_point_count(const GrPoint points[], GrScalar tol) {
482 GrScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]);
483 // TODO: fixed points sqrt
484 if (d < tol) {
485 return 1;
486 } else {
487 // Each time we subdivide, d should be cut in 4. So we need to
488 // subdivide x = log4(d/tol) times. x subdivisions creates 2^(x)
489 // points.
490 // 2^(log4(x)) = sqrt(x);
491 d = ceilf(sqrtf(d/tol));
492 return GrNextPow2((uint32_t)d);
493 }
494}
495
496static uint32_t generate_quadratic_points(const GrPoint& p0,
497 const GrPoint& p1,
498 const GrPoint& p2,
499 GrScalar tolSqd,
500 GrPoint** points,
501 uint32_t pointsLeft) {
502 if (pointsLeft < 2 ||
503 (p1.distanceToLineSegmentBetweenSqd(p0, p2)) < tolSqd) {
504 (*points)[0] = p2;
505 *points += 1;
506 return 1;
507 }
508
509 GrPoint q[] = {
510 GrPoint(GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY)),
511 GrPoint(GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY)),
512 };
513 GrPoint r(GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY));
514
515 pointsLeft >>= 1;
516 uint32_t a = generate_quadratic_points(p0, q[0], r, tolSqd, points, pointsLeft);
517 uint32_t b = generate_quadratic_points(r, q[1], p2, tolSqd, points, pointsLeft);
518 return a + b;
519}
520
521static uint32_t cubic_point_count(const GrPoint points[], GrScalar tol) {
522 GrScalar d = GrMax(points[1].distanceToLineSegmentBetweenSqd(points[0], points[3]),
523 points[2].distanceToLineSegmentBetweenSqd(points[0], points[3]));
524 d = sqrtf(d);
525 if (d < tol) {
526 return 1;
527 } else {
528 d = ceilf(sqrtf(d/tol));
529 return GrNextPow2((uint32_t)d);
530 }
531}
532
533static uint32_t generate_cubic_points(const GrPoint& p0,
534 const GrPoint& p1,
535 const GrPoint& p2,
536 const GrPoint& p3,
537 GrScalar tolSqd,
538 GrPoint** points,
539 uint32_t pointsLeft) {
540 if (pointsLeft < 2 ||
541 (p1.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd &&
542 p2.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd)) {
543 (*points)[0] = p3;
544 *points += 1;
545 return 1;
546 }
547 GrPoint q[] = {
548 GrPoint(GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY)),
549 GrPoint(GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY)),
550 GrPoint(GrScalarAve(p2.fX, p3.fX), GrScalarAve(p2.fY, p3.fY))
551 };
552 GrPoint r[] = {
553 GrPoint(GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY)),
554 GrPoint(GrScalarAve(q[1].fX, q[2].fX), GrScalarAve(q[1].fY, q[2].fY))
555 };
556 GrPoint s(GrScalarAve(r[0].fX, r[1].fX), GrScalarAve(r[0].fY, r[1].fY));
557 pointsLeft >>= 1;
558 uint32_t a = generate_cubic_points(p0, q[0], r[0], s, tolSqd, points, pointsLeft);
559 uint32_t b = generate_cubic_points(s, r[1], q[2], p3, tolSqd, points, pointsLeft);
560 return a + b;
561}
562
563#else // !NEW_EVAL
564
565static GrScalar gr_eval_quad(const GrScalar coord[], GrScalar t) {
566 GrScalar A = coord[0] - 2 * coord[2] + coord[4];
567 GrScalar B = 2 * (coord[2] - coord[0]);
568 GrScalar C = coord[0];
569
570 return GrMul(GrMul(A, t) + B, t) + C;
571}
572
573static void gr_eval_quad_at(const GrPoint src[3], GrScalar t, GrPoint* pt) {
574 GrAssert(src);
575 GrAssert(pt);
576 GrAssert(t >= 0 && t <= GR_Scalar1);
577 pt->set(gr_eval_quad(&src[0].fX, t), gr_eval_quad(&src[0].fY, t));
578}
579
580static GrScalar gr_eval_cubic(const GrScalar coord[], GrScalar t) {
581 GrScalar A = coord[6] - coord[0] + 3 * (coord[2] - coord[4]);
582 GrScalar B = 3 * (coord[0] - 2 * coord[2] + coord[4]);
583 GrScalar C = 3 * (coord[2] - coord[0]);
584 GrScalar D = coord[0];
585
586 return GrMul(GrMul(GrMul(A, t) + B, t) + C, t) + D;
587}
588
589static void gr_eval_cubic_at(const GrPoint src[4], GrScalar t, GrPoint* pt) {
590 GrAssert(src);
591 GrAssert(pt);
592 GrAssert(t >= 0 && t <= GR_Scalar1);
593
594 pt->set(gr_eval_cubic(&src[0].fX, t), gr_eval_cubic(&src[0].fY, t));
595}
596
597#endif // !NEW_EVAL
598
599static int worst_case_point_count(GrPathIter* path,
600 int* subpaths,
601 const GrMatrix& matrix,
602 GrScalar tol) {
603 int pointCount = 0;
604 *subpaths = 1;
605
606 bool first = true;
607
608 GrPathIter::Command cmd;
609
610 GrPoint pts[4];
611 while ((cmd = path->next(pts)) != GrPathIter::kEnd_Command) {
612
613 switch (cmd) {
614 case GrPathIter::kLine_Command:
615 pointCount += 1;
616 break;
617 case GrPathIter::kQuadratic_Command:
618#if NEW_EVAL
619 matrix.mapPoints(pts, pts, 3);
620 pointCount += quadratic_point_count(pts, tol);
621#else
622 pointCount += 9;
623#endif
624 break;
625 case GrPathIter::kCubic_Command:
626#if NEW_EVAL
627 matrix.mapPoints(pts, pts, 4);
628 pointCount += cubic_point_count(pts, tol);
629#else
630 pointCount += 17;
631#endif
632 break;
633 case GrPathIter::kMove_Command:
634 pointCount += 1;
635 if (!first) {
636 ++(*subpaths);
637 }
638 break;
639 default:
640 break;
641 }
642 first = false;
643 }
644 return pointCount;
645}
646
647static inline bool single_pass_path(const GrPathIter& path,
648 GrContext::PathFills fill,
reed@google.comac10a2d2010-12-22 21:39:39 +0000649 const GrGpu& gpu) {
650#if STENCIL_OFF
651 return true;
652#else
653 if (GrContext::kEvenOdd_PathFill == fill) {
654 GrPathIter::ConvexHint hint = path.hint();
655 return hint == GrPathIter::kConvex_ConvexHint ||
656 hint == GrPathIter::kNonOverlappingConvexPieces_ConvexHint;
657 } else if (GrContext::kWinding_PathFill == fill) {
658 GrPathIter::ConvexHint hint = path.hint();
659 return hint == GrPathIter::kConvex_ConvexHint ||
660 hint == GrPathIter::kNonOverlappingConvexPieces_ConvexHint ||
661 (hint == GrPathIter::kSameWindingConvexPieces_ConvexHint &&
662 gpu.canDisableBlend() && !gpu.isDitherState());
663
664 }
665 return false;
666#endif
667}
668
bsalomon@google.com5782d712011-01-21 21:03:59 +0000669void GrContext::drawPath(const GrPaint& paint,
670 GrPathIter* path,
671 PathFills fill,
672 const GrPoint* translate) {
reed@google.comac10a2d2010-12-22 21:39:39 +0000673
reed@google.comac10a2d2010-12-22 21:39:39 +0000674
bsalomon@google.com5782d712011-01-21 21:03:59 +0000675 this->prepareToDraw(paint);
676
677 GrDrawTarget::AutoStateRestore asr(fGpu);
reed@google.comac10a2d2010-12-22 21:39:39 +0000678
679#if NEW_EVAL
bsalomon@google.com5782d712011-01-21 21:03:59 +0000680 GrMatrix viewM = fGpu->getViewMatrix();
reed@google.comac10a2d2010-12-22 21:39:39 +0000681 // In order to tesselate the path we get a bound on how much the matrix can
682 // stretch when mapping to screen coordinates.
683 GrScalar stretch = viewM.getMaxStretch();
684 bool useStretch = stretch > 0;
685 GrScalar tol = EVAL_TOL;
686 if (!useStretch) {
687 // TODO: deal with perspective in some better way.
688 tol /= 10;
689 } else {
690 // TODO: fixed point divide
691 GrScalar sinv = 1 / stretch;
692 tol = GrMul(tol, sinv);
693 viewM = GrMatrix::I();
694 }
695 GrScalar tolSqd = GrMul(tol, tol);
696#else
697 // pass to worst_case... but won't be used.
698 static const GrScalar tol = -1;
699#endif
700
701 int subpathCnt;
702 int maxPts = worst_case_point_count(path,
703 &subpathCnt,
704#if CPU_TRANSFORM
705 cpuMatrix,
706#else
707 GrMatrix::I(),
708#endif
709 tol);
710 GrVertexLayout layout = 0;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000711
712 if (NULL != paint.getTexture()) {
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000713 layout = GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0);
reed@google.comac10a2d2010-12-22 21:39:39 +0000714 }
715 // add 4 to hold the bounding rect
716 GrDrawTarget::AutoReleaseGeometry arg(fGpu, layout, maxPts + 4, 0);
717
718 GrPoint* base = (GrPoint*) arg.vertices();
719 GrPoint* vert = base;
720 GrPoint* subpathBase = base;
721
722 GrAutoSTMalloc<8, uint16_t> subpathVertCount(subpathCnt);
723
724 path->rewind();
725
726 // TODO: use primitve restart if available rather than multiple draws
bsalomon@google.com5782d712011-01-21 21:03:59 +0000727 GrDrawTarget::PrimitiveType type;
reed@google.comac10a2d2010-12-22 21:39:39 +0000728 int passCount = 0;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000729 GrDrawTarget::StencilPass passes[3];
reed@google.comac10a2d2010-12-22 21:39:39 +0000730 bool reverse = false;
731
732 if (kHairLine_PathFill == fill) {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000733 type = GrDrawTarget::kLineStrip_PrimitiveType;
reed@google.comac10a2d2010-12-22 21:39:39 +0000734 passCount = 1;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000735 passes[0] = GrDrawTarget::kNone_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000736 } else {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000737 type = GrDrawTarget::kTriangleFan_PrimitiveType;
738 if (single_pass_path(*path, fill, *fGpu)) {
reed@google.comac10a2d2010-12-22 21:39:39 +0000739 passCount = 1;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000740 passes[0] = GrDrawTarget::kNone_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000741 } else {
742 switch (fill) {
743 case kInverseEvenOdd_PathFill:
744 reverse = true;
745 // fallthrough
746 case kEvenOdd_PathFill:
747 passCount = 2;
bsalomon@google.com5782d712011-01-21 21:03:59 +0000748 passes[0] = GrDrawTarget::kEvenOddStencil_StencilPass;
749 passes[1] = GrDrawTarget::kEvenOddColor_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000750 break;
751
752 case kInverseWinding_PathFill:
753 reverse = true;
754 // fallthrough
755 case kWinding_PathFill:
bsalomon@google.com5782d712011-01-21 21:03:59 +0000756 passes[0] = GrDrawTarget::kWindingStencil1_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000757 if (fGpu->supportsSingleStencilPassWinding()) {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000758 passes[1] = GrDrawTarget::kWindingColor_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000759 passCount = 2;
760 } else {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000761 passes[1] = GrDrawTarget::kWindingStencil2_StencilPass;
762 passes[2] = GrDrawTarget::kWindingColor_StencilPass;
reed@google.comac10a2d2010-12-22 21:39:39 +0000763 passCount = 3;
764 }
765 break;
766 default:
767 GrAssert(!"Unknown path fill!");
768 return;
769 }
770 }
771 }
772 fGpu->setReverseFill(reverse);
773#if CPU_TRANSFORM
774 GrMatrix cpuMatrix;
775 fGpu->getViewMatrix(&cpuMatrix);
776 fGpu->setViewMatrix(GrMatrix::I());
777#endif
778
779 GrPoint pts[4];
780
781 bool first = true;
782 int subpath = 0;
783
784 for (;;) {
785 GrPathIter::Command cmd = path->next(pts);
786#if CPU_TRANSFORM
787 int numPts = GrPathIter::NumCommandPoints(cmd);
788 cpuMatrix.mapPoints(pts, pts, numPts);
789#endif
790 switch (cmd) {
791 case GrPathIter::kMove_Command:
792 if (!first) {
793 subpathVertCount[subpath] = vert-subpathBase;
794 subpathBase = vert;
795 ++subpath;
796 }
797 *vert = pts[0];
798 vert++;
799 break;
800 case GrPathIter::kLine_Command:
801 *vert = pts[1];
802 vert++;
803 break;
804 case GrPathIter::kQuadratic_Command: {
805#if NEW_EVAL
806
807 generate_quadratic_points(pts[0], pts[1], pts[2],
808 tolSqd, &vert,
809 quadratic_point_count(pts, tol));
810#else
811 const int n = 8;
812 const GrScalar dt = GR_Scalar1 / n;
813 GrScalar t = dt;
814 for (int i = 1; i < n; i++) {
815 gr_eval_quad_at(pts, t, (GrPoint*)vert);
816 t += dt;
817 vert++;
818 }
819 vert->set(pts[2].fX, pts[2].fY);
820 vert++;
821#endif
822 break;
823 }
824 case GrPathIter::kCubic_Command: {
825#if NEW_EVAL
826 generate_cubic_points(pts[0], pts[1], pts[2], pts[3],
827 tolSqd, &vert,
828 cubic_point_count(pts, tol));
829#else
830 const int n = 16;
831 const GrScalar dt = GR_Scalar1 / n;
832 GrScalar t = dt;
833 for (int i = 1; i < n; i++) {
834 gr_eval_cubic_at(pts, t, (GrPoint*)vert);
835 t += dt;
836 vert++;
837 }
838 vert->set(pts[3].fX, pts[3].fY);
839 vert++;
840#endif
841 break;
842 }
843 case GrPathIter::kClose_Command:
844 break;
845 case GrPathIter::kEnd_Command:
846 subpathVertCount[subpath] = vert-subpathBase;
847 ++subpath; // this could be only in debug
848 goto FINISHED;
849 }
850 first = false;
851 }
852FINISHED:
853 GrAssert(subpath == subpathCnt);
854 GrAssert((vert - base) <= maxPts);
855
856 if (translate) {
857 int count = vert - base;
858 for (int i = 0; i < count; i++) {
859 base[i].offset(translate->fX, translate->fY);
860 }
861 }
862
863 // arbitrary path complexity cutoff
864 bool useBounds = fill != kHairLine_PathFill &&
865 (reverse || (vert - base) > 8);
866 GrPoint* boundsVerts = base + maxPts;
867 if (useBounds) {
868 GrRect bounds;
869 if (reverse) {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000870 GrAssert(NULL != fGpu->getRenderTarget());
reed@google.comac10a2d2010-12-22 21:39:39 +0000871 // draw over the whole world.
872 bounds.setLTRB(0, 0,
bsalomon@google.com5782d712011-01-21 21:03:59 +0000873 GrIntToScalar(fGpu->getRenderTarget()->width()),
874 GrIntToScalar(fGpu->getRenderTarget()->height()));
875 GrMatrix vmi;
876 if (fGpu->getViewInverse(&vmi)) {
877 vmi.mapRect(&bounds);
878 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000879 } else {
880 bounds.setBounds((GrPoint*)base, vert - base);
881 }
882 boundsVerts[0].setRectFan(bounds.fLeft, bounds.fTop, bounds.fRight,
883 bounds.fBottom);
884 }
885
886 for (int p = 0; p < passCount; ++p) {
887 fGpu->setStencilPass(passes[p]);
bsalomon@google.com5782d712011-01-21 21:03:59 +0000888 if (useBounds && (GrDrawTarget::kEvenOddColor_StencilPass == passes[p] ||
889 GrDrawTarget::kWindingColor_StencilPass == passes[p])) {
890 fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType,
reed@google.comac10a2d2010-12-22 21:39:39 +0000891 maxPts, 4);
bsalomon@google.com5782d712011-01-21 21:03:59 +0000892
reed@google.comac10a2d2010-12-22 21:39:39 +0000893 } else {
894 int baseVertex = 0;
895 for (int sp = 0; sp < subpathCnt; ++sp) {
896 fGpu->drawNonIndexed(type,
897 baseVertex,
898 subpathVertCount[sp]);
899 baseVertex += subpathVertCount[sp];
900 }
901 }
902 }
903}
904
bsalomon@google.com5782d712011-01-21 21:03:59 +0000905////////////////////////////////////////////////////////////////////////////////
906
reed@google.comac10a2d2010-12-22 21:39:39 +0000907void GrContext::flush(bool flushRenderTarget) {
908 flushText();
909 if (flushRenderTarget) {
910 fGpu->forceRenderTargetFlush();
911 }
912}
913
914void GrContext::flushText() {
915 fTextDrawBuffer.playback(fGpu);
916 fTextDrawBuffer.reset();
917}
918
919bool GrContext::readPixels(int left, int top, int width, int height,
920 GrTexture::PixelConfig config, void* buffer) {
921 this->flush(true);
922 return fGpu->readPixels(left, top, width, height, config, buffer);
923}
924
925void GrContext::writePixels(int left, int top, int width, int height,
926 GrTexture::PixelConfig config, const void* buffer,
927 size_t stride) {
bsalomon@google.com5782d712011-01-21 21:03:59 +0000928
929 // TODO: when underlying api has a direct way to do this we should use it
930 // (e.g. glDrawPixels on desktop GL).
931
reed@google.comac10a2d2010-12-22 21:39:39 +0000932 const GrGpu::TextureDesc desc = {
933 0, GrGpu::kNone_AALevel, width, height, config
934 };
935 GrTexture* texture = fGpu->createTexture(desc, buffer, stride);
936 if (NULL == texture) {
937 return;
938 }
939
940 this->flush(true);
941
942 GrAutoUnref aur(texture);
943 GrDrawTarget::AutoStateRestore asr(fGpu);
944
945 GrMatrix matrix;
946 matrix.setTranslate(GrIntToScalar(left), GrIntToScalar(top));
947 fGpu->setViewMatrix(matrix);
948 matrix.setScale(GR_Scalar1 / texture->allocWidth(),
949 GR_Scalar1 / texture->allocHeight());
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000950 fGpu->setTextureMatrix(0, matrix);
reed@google.comac10a2d2010-12-22 21:39:39 +0000951
952 fGpu->disableState(GrDrawTarget::kClip_StateBit);
953 fGpu->setAlpha(0xFF);
954 fGpu->setBlendFunc(GrDrawTarget::kOne_BlendCoeff,
955 GrDrawTarget::kZero_BlendCoeff);
bsalomon@google.com8531c1c2011-01-13 19:52:45 +0000956 fGpu->setTexture(0, texture);
957 fGpu->setSamplerState(0, GrSamplerState::ClampNoFilter());
reed@google.comac10a2d2010-12-22 21:39:39 +0000958
bsalomon@google.com5782d712011-01-21 21:03:59 +0000959 GrVertexLayout layout = GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0);
960 static const int VCOUNT = 4;
961
962 GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, VCOUNT, 0);
963 if (!geo.succeeded()) {
964 return;
965 }
966 ((GrPoint*)geo.vertices())->setIRectFan(0, 0, width, height);
967 fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, 0, VCOUNT);
968}
969////////////////////////////////////////////////////////////////////////////////
970
971void GrContext::SetPaint(const GrPaint& paint, GrDrawTarget* target) {
972 target->setTexture(0, paint.getTexture());
973 target->setTextureMatrix(0, paint.fTextureMatrix);
974 target->setSamplerState(0, paint.fSampler);
975 target->setColor(paint.fColor);
976
977 if (paint.fDither) {
978 target->enableState(GrDrawTarget::kDither_StateBit);
979 } else {
980 target->disableState(GrDrawTarget::kDither_StateBit);
981 }
982 if (paint.fAntiAlias) {
983 target->enableState(GrDrawTarget::kAntialias_StateBit);
984 } else {
985 target->disableState(GrDrawTarget::kAntialias_StateBit);
986 }
987 target->setBlendFunc(paint.fSrcBlendCoeff, paint.fDstBlendCoeff);
988}
989
990void GrContext::prepareToDraw(const GrPaint& paint) {
991
992 flushText();
993 SetPaint(paint, fGpu);
reed@google.comac10a2d2010-12-22 21:39:39 +0000994}
995
996////////////////////////////////////////////////////////////////////////////////
997
reed@google.comac10a2d2010-12-22 21:39:39 +0000998void GrContext::resetContext() {
999 fGpu->resetContext();
1000}
1001
reed@google.comac10a2d2010-12-22 21:39:39 +00001002void GrContext::setRenderTarget(GrRenderTarget* target) {
1003 flushText();
1004 fGpu->setRenderTarget(target);
1005}
1006
bsalomon@google.com5782d712011-01-21 21:03:59 +00001007GrRenderTarget* GrContext::getRenderTarget() {
1008 return fGpu->getRenderTarget();
reed@google.comac10a2d2010-12-22 21:39:39 +00001009}
1010
bsalomon@google.com5782d712011-01-21 21:03:59 +00001011const GrRenderTarget* GrContext::getRenderTarget() const {
1012 return fGpu->getRenderTarget();
reed@google.comac10a2d2010-12-22 21:39:39 +00001013}
1014
bsalomon@google.com5782d712011-01-21 21:03:59 +00001015const GrMatrix& GrContext::getMatrix() const {
1016 return fGpu->getViewMatrix();
reed@google.comac10a2d2010-12-22 21:39:39 +00001017}
1018
bsalomon@google.com5782d712011-01-21 21:03:59 +00001019void GrContext::setMatrix(const GrMatrix& m) {
reed@google.comac10a2d2010-12-22 21:39:39 +00001020 fGpu->setViewMatrix(m);
1021}
1022
bsalomon@google.com5782d712011-01-21 21:03:59 +00001023void GrContext::concatMatrix(const GrMatrix& m) const {
1024 fGpu->concatViewMatrix(m);
reed@google.comac10a2d2010-12-22 21:39:39 +00001025}
1026
1027static inline intptr_t setOrClear(intptr_t bits, int shift, intptr_t pred) {
1028 intptr_t mask = 1 << shift;
1029 if (pred) {
1030 bits |= mask;
1031 } else {
1032 bits &= ~mask;
1033 }
1034 return bits;
1035}
1036
reed@google.comac10a2d2010-12-22 21:39:39 +00001037void GrContext::resetStats() {
1038 fGpu->resetStats();
1039}
1040
1041const GrGpu::Stats& GrContext::getStats() const {
1042 return fGpu->getStats();
1043}
1044
1045void GrContext::printStats() const {
1046 fGpu->printStats();
1047}
1048
1049GrContext::GrContext(GrGpu* gpu) :
1050 fVBAllocPool(gpu,
1051 gpu->supportsBufferLocking() ? POOL_VB_SIZE : 0,
1052 gpu->supportsBufferLocking() ? NUM_POOL_VBS : 0),
1053 fTextDrawBuffer(gpu->supportsBufferLocking() ? &fVBAllocPool : NULL) {
1054 fGpu = gpu;
1055 fGpu->ref();
1056 fTextureCache = new GrTextureCache(MAX_TEXTURE_CACHE_COUNT,
1057 MAX_TEXTURE_CACHE_BYTES);
1058 fFontCache = new GrFontCache(fGpu);
1059}
1060
1061bool GrContext::finalizeTextureKey(GrTextureKey* key,
1062 const GrSamplerState& sampler) const {
1063 uint32_t bits = 0;
1064 uint16_t width = key->width();
1065 uint16_t height = key->height();
1066 if (fGpu->npotTextureSupport() < GrGpu::kNonRendertarget_NPOTTextureType) {
1067 if ((sampler.getWrapX() != GrSamplerState::kClamp_WrapMode ||
1068 sampler.getWrapY() != GrSamplerState::kClamp_WrapMode) &&
1069 (!GrIsPow2(width) || !GrIsPow2(height))) {
1070 bits |= 1;
1071 bits |= sampler.isFilter() ? 2 : 0;
1072 }
1073 }
1074 key->finalize(bits);
1075 return 0 != bits;
1076}
1077
bsalomon@google.com5782d712011-01-21 21:03:59 +00001078GrDrawTarget* GrContext::getTextTarget(const GrPaint& paint) {
1079 GrDrawTarget* target;
reed@google.comac10a2d2010-12-22 21:39:39 +00001080#if DEFER_TEXT_RENDERING
1081 fTextDrawBuffer.initializeDrawStateAndClip(*fGpu);
bsalomon@google.com5782d712011-01-21 21:03:59 +00001082 target = &fTextDrawBuffer;
reed@google.comac10a2d2010-12-22 21:39:39 +00001083#else
bsalomon@google.com5782d712011-01-21 21:03:59 +00001084 target = fGpu;
reed@google.comac10a2d2010-12-22 21:39:39 +00001085#endif
bsalomon@google.com5782d712011-01-21 21:03:59 +00001086 SetPaint(paint, target);
1087 return target;
reed@google.comac10a2d2010-12-22 21:39:39 +00001088}
1089
1090const GrIndexBuffer* GrContext::quadIndexBuffer() const {
1091 return fGpu->quadIndexBuffer();
1092}
1093
1094int GrContext::maxQuadsInIndexBuffer() const {
1095 return fGpu->maxQuadsInIndexBuffer();
1096}
1097
1098
1099