blob: de97f68291701451f5560fb07d3f3d7c83a6a197 [file] [log] [blame]
bsalomon@google.comaf84e742012-10-05 13:23:24 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrDrawState.h"
bsalomon@google.comaf84e742012-10-05 13:23:24 +00009#include "GrPaint.h"
bsalomon62c447d2014-08-08 08:08:50 -070010#include "GrDrawTargetCaps.h"
bsalomon@google.comaf84e742012-10-05 13:23:24 +000011
bsalomon8f727332014-08-05 07:50:06 -070012//////////////////////////////////////////////////////////////////////////////s
13
14GrDrawState::CombinedState GrDrawState::CombineIfPossible(
bsalomon62c447d2014-08-08 08:08:50 -070015 const GrDrawState& a, const GrDrawState& b, const GrDrawTargetCaps& caps) {
bsalomon8f727332014-08-05 07:50:06 -070016
17 bool usingVertexColors = a.hasColorVertexAttribute();
18 if (!usingVertexColors && a.fColor != b.fColor) {
19 return kIncompatible_CombinedState;
20 }
21
22 if (a.fRenderTarget.get() != b.fRenderTarget.get() ||
egdaniel8cbf3d52014-08-21 06:27:22 -070023 a.fColorStages.count() != b.fColorStages.count() ||
24 a.fCoverageStages.count() != b.fCoverageStages.count() ||
bsalomon8f727332014-08-05 07:50:06 -070025 !a.fViewMatrix.cheapEqualTo(b.fViewMatrix) ||
egdaniel8cbf3d52014-08-21 06:27:22 -070026 a.fSrcBlend != b.fSrcBlend ||
27 a.fDstBlend != b.fDstBlend ||
bsalomon8f727332014-08-05 07:50:06 -070028 a.fBlendConstant != b.fBlendConstant ||
29 a.fFlagBits != b.fFlagBits ||
30 a.fVACount != b.fVACount ||
31 memcmp(a.fVAPtr, b.fVAPtr, a.fVACount * sizeof(GrVertexAttrib)) ||
32 a.fStencilSettings != b.fStencilSettings ||
33 a.fDrawFace != b.fDrawFace) {
34 return kIncompatible_CombinedState;
35 }
36
37 bool usingVertexCoverage = a.hasCoverageVertexAttribute();
38 if (!usingVertexCoverage && a.fCoverage != b.fCoverage) {
39 return kIncompatible_CombinedState;
40 }
41
42 bool explicitLocalCoords = a.hasLocalCoordAttribute();
egdaniel8cbf3d52014-08-21 06:27:22 -070043 for (int i = 0; i < a.numColorStages(); i++) {
44 if (!GrEffectStage::AreCompatible(a.getColorStage(i), b.getColorStage(i),
45 explicitLocalCoords)) {
46 return kIncompatible_CombinedState;
47 }
48 }
49 for (int i = 0; i < a.numCoverageStages(); i++) {
50 if (!GrEffectStage::AreCompatible(a.getCoverageStage(i), b.getCoverageStage(i),
51 explicitLocalCoords)) {
52 return kIncompatible_CombinedState;
53 }
54 }
egdaniel776bdbd2014-08-06 11:07:02 -070055
egdanielab78e062014-08-22 07:19:35 -070056 SkASSERT(a.fVertexSize == b.fVertexSize);
bsalomon8f727332014-08-05 07:50:06 -070057 SkASSERT(0 == memcmp(a.fFixedFunctionVertexAttribIndices,
58 b.fFixedFunctionVertexAttribIndices,
59 sizeof(a.fFixedFunctionVertexAttribIndices)));
bsalomon62c447d2014-08-08 08:08:50 -070060
61 if (usingVertexColors) {
62 // If one is opaque and the other is not then the combined state is not opaque. Moreover,
63 // if the opaqueness affects the ability to get color/coverage blending correct then we
64 // don't combine the draw states.
65 bool aIsOpaque = (kVertexColorsAreOpaque_Hint & a.fHints);
66 bool bIsOpaque = (kVertexColorsAreOpaque_Hint & b.fHints);
67 if (aIsOpaque != bIsOpaque) {
68 const GrDrawState* opaque;
69 const GrDrawState* nonOpaque;
70 if (aIsOpaque) {
71 opaque = &a;
72 nonOpaque = &b;
73 } else {
74 opaque = &b;
75 nonOpaque = &a;
76 }
77 if (!opaque->hasSolidCoverage() && opaque->couldApplyCoverage(caps)) {
78 SkASSERT(!nonOpaque->hasSolidCoverage());
79 if (!nonOpaque->couldApplyCoverage(caps)) {
80 return kIncompatible_CombinedState;
81 }
82 }
83 return aIsOpaque ? kB_CombinedState : kA_CombinedState;
84 }
85 }
bsalomon8f727332014-08-05 07:50:06 -070086 return kAOrB_CombinedState;
87}
88
bsalomon8f727332014-08-05 07:50:06 -070089//////////////////////////////////////////////////////////////////////////////s
90
91GrDrawState::GrDrawState(const GrDrawState& state, const SkMatrix& preConcatMatrix) {
92 SkDEBUGCODE(fBlockEffectRemovalCnt = 0;)
93 *this = state;
94 if (!preConcatMatrix.isIdentity()) {
egdaniel776bdbd2014-08-06 11:07:02 -070095 for (int i = 0; i < this->numColorStages(); ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -070096 fColorStages[i].localCoordChange(preConcatMatrix);
bsalomon8f727332014-08-05 07:50:06 -070097 }
egdaniel776bdbd2014-08-06 11:07:02 -070098 for (int i = 0; i < this->numCoverageStages(); ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -070099 fCoverageStages[i].localCoordChange(preConcatMatrix);
bsalomon8f727332014-08-05 07:50:06 -0700100 }
101 this->invalidateBlendOptFlags();
102 }
103}
104
105GrDrawState& GrDrawState::operator=(const GrDrawState& that) {
106 SkASSERT(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
107 this->setRenderTarget(that.fRenderTarget.get());
108 fColor = that.fColor;
109 fViewMatrix = that.fViewMatrix;
egdaniel8cbf3d52014-08-21 06:27:22 -0700110 fSrcBlend = that.fSrcBlend;
111 fDstBlend = that.fDstBlend;
bsalomon8f727332014-08-05 07:50:06 -0700112 fBlendConstant = that.fBlendConstant;
113 fFlagBits = that.fFlagBits;
114 fVACount = that.fVACount;
115 fVAPtr = that.fVAPtr;
egdanielab78e062014-08-22 07:19:35 -0700116 fVertexSize = that.fVertexSize;
bsalomon8f727332014-08-05 07:50:06 -0700117 fStencilSettings = that.fStencilSettings;
118 fCoverage = that.fCoverage;
119 fDrawFace = that.fDrawFace;
egdaniel8cbf3d52014-08-21 06:27:22 -0700120 fColorStages = that.fColorStages;
121 fCoverageStages = that.fCoverageStages;
bsalomon8f727332014-08-05 07:50:06 -0700122 fOptSrcBlend = that.fOptSrcBlend;
123 fOptDstBlend = that.fOptDstBlend;
124 fBlendOptFlags = that.fBlendOptFlags;
125
bsalomon62c447d2014-08-08 08:08:50 -0700126 fHints = that.fHints;
egdaniel776bdbd2014-08-06 11:07:02 -0700127
bsalomon8f727332014-08-05 07:50:06 -0700128 memcpy(fFixedFunctionVertexAttribIndices,
129 that.fFixedFunctionVertexAttribIndices,
130 sizeof(fFixedFunctionVertexAttribIndices));
131 return *this;
132}
133
134void GrDrawState::onReset(const SkMatrix* initialViewMatrix) {
135 SkASSERT(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
egdaniel8cbf3d52014-08-21 06:27:22 -0700136 fColorStages.reset();
137 fCoverageStages.reset();
bsalomon8f727332014-08-05 07:50:06 -0700138
139 fRenderTarget.reset(NULL);
140
141 this->setDefaultVertexAttribs();
142
143 fColor = 0xffffffff;
144 if (NULL == initialViewMatrix) {
145 fViewMatrix.reset();
146 } else {
147 fViewMatrix = *initialViewMatrix;
148 }
egdaniel8cbf3d52014-08-21 06:27:22 -0700149 fSrcBlend = kOne_GrBlendCoeff;
150 fDstBlend = kZero_GrBlendCoeff;
bsalomon8f727332014-08-05 07:50:06 -0700151 fBlendConstant = 0x0;
152 fFlagBits = 0x0;
153 fStencilSettings.setDisabled();
egdaniel8cbf3d52014-08-21 06:27:22 -0700154 fCoverage = 0xff;
bsalomon8f727332014-08-05 07:50:06 -0700155 fDrawFace = kBoth_DrawFace;
156
bsalomon62c447d2014-08-08 08:08:50 -0700157 fHints = 0;
158
bsalomon8f727332014-08-05 07:50:06 -0700159 this->invalidateBlendOptFlags();
160}
161
bsalomon@google.com137f1342013-05-29 21:27:53 +0000162bool GrDrawState::setIdentityViewMatrix() {
egdaniel776bdbd2014-08-06 11:07:02 -0700163 if (this->numTotalStages()) {
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000164 SkMatrix invVM;
bsalomon2ed5ef82014-07-07 08:44:05 -0700165 if (!fViewMatrix.invert(&invVM)) {
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000166 // sad trombone sound
167 return false;
168 }
egdaniel776bdbd2014-08-06 11:07:02 -0700169 for (int s = 0; s < this->numColorStages(); ++s) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700170 fColorStages[s].localCoordChange(invVM);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000171 }
egdaniel776bdbd2014-08-06 11:07:02 -0700172 for (int s = 0; s < this->numCoverageStages(); ++s) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700173 fCoverageStages[s].localCoordChange(invVM);
bsalomon@google.com137f1342013-05-29 21:27:53 +0000174 }
175 }
bsalomon2ed5ef82014-07-07 08:44:05 -0700176 fViewMatrix.reset();
bsalomon@google.com137f1342013-05-29 21:27:53 +0000177 return true;
178}
179
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000180void GrDrawState::setFromPaint(const GrPaint& paint, const SkMatrix& vm, GrRenderTarget* rt) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000181 SkASSERT(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000182
egdaniel8cbf3d52014-08-21 06:27:22 -0700183 fColorStages.reset();
184 fCoverageStages.reset();
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000185
commit-bot@chromium.org42dacab2013-07-13 17:24:24 +0000186 for (int i = 0; i < paint.numColorStages(); ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700187 fColorStages.push_back(paint.getColorStage(i));
bsalomon@google.comaf84e742012-10-05 13:23:24 +0000188 }
189
commit-bot@chromium.org42dacab2013-07-13 17:24:24 +0000190 for (int i = 0; i < paint.numCoverageStages(); ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700191 fCoverageStages.push_back(paint.getCoverageStage(i));
bsalomon@google.comaf84e742012-10-05 13:23:24 +0000192 }
193
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000194 this->setRenderTarget(rt);
bsalomon@google.comaf84e742012-10-05 13:23:24 +0000195
bsalomon2ed5ef82014-07-07 08:44:05 -0700196 fViewMatrix = vm;
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000197
198 // These have no equivalent in GrPaint, set them to defaults
bsalomon2ed5ef82014-07-07 08:44:05 -0700199 fBlendConstant = 0x0;
200 fDrawFace = kBoth_DrawFace;
201 fStencilSettings.setDisabled();
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000202 this->resetStateFlags();
bsalomon62c447d2014-08-08 08:08:50 -0700203 fHints = 0;
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000204
bsalomon@google.com21c10c52013-06-13 17:44:07 +0000205 // Enable the clip bit
206 this->enableState(GrDrawState::kClip_StateBit);
207
commit-bot@chromium.orgbb6a3172013-05-28 17:25:49 +0000208 this->setColor(paint.getColor());
bsalomon@google.comc7448ce2012-10-05 19:04:13 +0000209 this->setState(GrDrawState::kDither_StateBit, paint.isDither());
210 this->setState(GrDrawState::kHWAntialias_StateBit, paint.isAntiAlias());
bsalomon@google.comaf84e742012-10-05 13:23:24 +0000211
bsalomon@google.comc7448ce2012-10-05 19:04:13 +0000212 this->setBlendFunc(paint.getSrcBlendCoeff(), paint.getDstBlendCoeff());
bsalomon@google.comc7448ce2012-10-05 19:04:13 +0000213 this->setCoverage(paint.getCoverage());
egdaniel9514d242014-07-18 06:15:43 -0700214 this->invalidateBlendOptFlags();
bsalomon@google.comaf84e742012-10-05 13:23:24 +0000215}
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000216
217////////////////////////////////////////////////////////////////////////////////
218
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000219static size_t vertex_size(const GrVertexAttrib* attribs, int count) {
220 // this works as long as we're 4 byte-aligned
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000221#ifdef SK_DEBUG
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000222 uint32_t overlapCheck = 0;
223#endif
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000224 SkASSERT(count <= GrDrawState::kMaxVertexAttribCnt);
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000225 size_t size = 0;
226 for (int index = 0; index < count; ++index) {
jvanverth@google.com054ae992013-04-01 20:06:51 +0000227 size_t attribSize = GrVertexAttribTypeSize(attribs[index].fType);
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000228 size += attribSize;
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000229#ifdef SK_DEBUG
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000230 size_t dwordCount = attribSize >> 2;
231 uint32_t mask = (1 << dwordCount)-1;
232 size_t offsetShift = attribs[index].fOffset >> 2;
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000233 SkASSERT(!(overlapCheck & (mask << offsetShift)));
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000234 overlapCheck |= (mask << offsetShift);
235#endif
jvanverth@google.comcc782382013-01-28 20:39:48 +0000236 }
237 return size;
238}
239
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000240size_t GrDrawState::getVertexSize() const {
egdanielab78e062014-08-22 07:19:35 -0700241 return fVertexSize;
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000242}
243
jvanverth@google.comcc782382013-01-28 20:39:48 +0000244////////////////////////////////////////////////////////////////////////////////
245
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000246void GrDrawState::setVertexAttribs(const GrVertexAttrib* attribs, int count) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000247 SkASSERT(count <= kMaxVertexAttribCnt);
robertphillips@google.com42903302013-04-20 12:26:07 +0000248
bsalomon2ed5ef82014-07-07 08:44:05 -0700249 fVAPtr = attribs;
250 fVACount = count;
egdanielab78e062014-08-22 07:19:35 -0700251 fVertexSize = vertex_size(fVAPtr, fVACount);
jvanverth@google.com054ae992013-04-01 20:06:51 +0000252
253 // Set all the indices to -1
bsalomon2ed5ef82014-07-07 08:44:05 -0700254 memset(fFixedFunctionVertexAttribIndices,
jvanverth@google.com054ae992013-04-01 20:06:51 +0000255 0xff,
bsalomon2ed5ef82014-07-07 08:44:05 -0700256 sizeof(fFixedFunctionVertexAttribIndices));
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000257#ifdef SK_DEBUG
jvanverth@google.com054ae992013-04-01 20:06:51 +0000258 uint32_t overlapCheck = 0;
259#endif
260 for (int i = 0; i < count; ++i) {
261 if (attribs[i].fBinding < kGrFixedFunctionVertexAttribBindingCnt) {
262 // The fixed function attribs can only be specified once
bsalomon2ed5ef82014-07-07 08:44:05 -0700263 SkASSERT(-1 == fFixedFunctionVertexAttribIndices[attribs[i].fBinding]);
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000264 SkASSERT(GrFixedFunctionVertexAttribVectorCount(attribs[i].fBinding) ==
jvanverth@google.com054ae992013-04-01 20:06:51 +0000265 GrVertexAttribTypeVectorCount(attribs[i].fType));
bsalomon2ed5ef82014-07-07 08:44:05 -0700266 fFixedFunctionVertexAttribIndices[attribs[i].fBinding] = i;
jvanverth@google.com054ae992013-04-01 20:06:51 +0000267 }
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000268#ifdef SK_DEBUG
jvanverth@google.com054ae992013-04-01 20:06:51 +0000269 size_t dwordCount = GrVertexAttribTypeSize(attribs[i].fType) >> 2;
270 uint32_t mask = (1 << dwordCount)-1;
271 size_t offsetShift = attribs[i].fOffset >> 2;
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000272 SkASSERT(!(overlapCheck & (mask << offsetShift)));
jvanverth@google.com054ae992013-04-01 20:06:51 +0000273 overlapCheck |= (mask << offsetShift);
274#endif
jvanverth@google.comcc782382013-01-28 20:39:48 +0000275 }
egdaniel9514d242014-07-18 06:15:43 -0700276 this->invalidateBlendOptFlags();
jvanverth@google.com054ae992013-04-01 20:06:51 +0000277 // Positions must be specified.
bsalomon2ed5ef82014-07-07 08:44:05 -0700278 SkASSERT(-1 != fFixedFunctionVertexAttribIndices[kPosition_GrVertexAttribBinding]);
jvanverth@google.comcc782382013-01-28 20:39:48 +0000279}
280
281////////////////////////////////////////////////////////////////////////////////
282
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000283void GrDrawState::setDefaultVertexAttribs() {
jvanverth@google.com054ae992013-04-01 20:06:51 +0000284 static const GrVertexAttrib kPositionAttrib =
285 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding};
robertphillips@google.com42903302013-04-20 12:26:07 +0000286
bsalomon2ed5ef82014-07-07 08:44:05 -0700287 fVAPtr = &kPositionAttrib;
288 fVACount = 1;
egdanielab78e062014-08-22 07:19:35 -0700289 fVertexSize = GrVertexAttribTypeSize(kVec2f_GrVertexAttribType);
robertphillips@google.com42903302013-04-20 12:26:07 +0000290
jvanverth@google.com054ae992013-04-01 20:06:51 +0000291 // set all the fixed function indices to -1 except position.
bsalomon2ed5ef82014-07-07 08:44:05 -0700292 memset(fFixedFunctionVertexAttribIndices,
jvanverth@google.com054ae992013-04-01 20:06:51 +0000293 0xff,
bsalomon2ed5ef82014-07-07 08:44:05 -0700294 sizeof(fFixedFunctionVertexAttribIndices));
295 fFixedFunctionVertexAttribIndices[kPosition_GrVertexAttribBinding] = 0;
egdaniel9514d242014-07-18 06:15:43 -0700296 this->invalidateBlendOptFlags();
jvanverth@google.comcc782382013-01-28 20:39:48 +0000297}
298
299////////////////////////////////////////////////////////////////////////////////
300
commit-bot@chromium.orgff6ea262013-03-12 12:26:08 +0000301bool GrDrawState::validateVertexAttribs() const {
jvanverth@google.com054ae992013-04-01 20:06:51 +0000302 // check consistency of effects and attributes
303 GrSLType slTypes[kMaxVertexAttribCnt];
304 for (int i = 0; i < kMaxVertexAttribCnt; ++i) {
305 slTypes[i] = static_cast<GrSLType>(-1);
commit-bot@chromium.orgff6ea262013-03-12 12:26:08 +0000306 }
egdaniel776bdbd2014-08-06 11:07:02 -0700307 int totalStages = this->numTotalStages();
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000308 for (int s = 0; s < totalStages; ++s) {
egdaniel776bdbd2014-08-06 11:07:02 -0700309 int covIdx = s - this->numColorStages();
310 const GrEffectStage& stage = covIdx < 0 ? this->getColorStage(s) :
311 this->getCoverageStage(covIdx);
bsalomonf99f8842014-07-07 11:54:23 -0700312 const GrEffect* effect = stage.getEffect();
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000313 SkASSERT(NULL != effect);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000314 // make sure that any attribute indices have the correct binding type, that the attrib
315 // type and effect's shader lang type are compatible, and that attributes shared by
316 // multiple effects use the same shader lang type.
317 const int* attributeIndices = stage.getVertexAttribIndices();
318 int numAttributes = stage.getVertexAttribIndexCount();
319 for (int i = 0; i < numAttributes; ++i) {
320 int attribIndex = attributeIndices[i];
bsalomon2ed5ef82014-07-07 08:44:05 -0700321 if (attribIndex >= fVACount ||
322 kEffect_GrVertexAttribBinding != fVAPtr[attribIndex].fBinding) {
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000323 return false;
jvanverth@google.comc7bf2962013-04-01 19:29:32 +0000324 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000325
bsalomonf99f8842014-07-07 11:54:23 -0700326 GrSLType effectSLType = effect->vertexAttribType(i);
bsalomon2ed5ef82014-07-07 08:44:05 -0700327 GrVertexAttribType attribType = fVAPtr[attribIndex].fType;
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000328 int slVecCount = GrSLTypeVectorCount(effectSLType);
329 int attribVecCount = GrVertexAttribTypeVectorCount(attribType);
330 if (slVecCount != attribVecCount ||
331 (static_cast<GrSLType>(-1) != slTypes[attribIndex] &&
332 slTypes[attribIndex] != effectSLType)) {
333 return false;
334 }
335 slTypes[attribIndex] = effectSLType;
commit-bot@chromium.orgff6ea262013-03-12 12:26:08 +0000336 }
337 }
338
339 return true;
340}
341
bsalomon@google.comd09ab842013-05-15 17:30:26 +0000342bool GrDrawState::willEffectReadDstColor() const {
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000343 if (!this->isColorWriteDisabled()) {
egdaniel776bdbd2014-08-06 11:07:02 -0700344 for (int s = 0; s < this->numColorStages(); ++s) {
345 if (this->getColorStage(s).getEffect()->willReadDstColor()) {
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000346 return true;
347 }
348 }
349 }
egdaniel776bdbd2014-08-06 11:07:02 -0700350 for (int s = 0; s < this->numCoverageStages(); ++s) {
351 if (this->getCoverageStage(s).getEffect()->willReadDstColor()) {
bsalomon@google.comd09ab842013-05-15 17:30:26 +0000352 return true;
353 }
354 }
355 return false;
356}
357
jvanverth@google.comcc782382013-01-28 20:39:48 +0000358////////////////////////////////////////////////////////////////////////////////
359
bsalomon62c447d2014-08-08 08:08:50 -0700360bool GrDrawState::couldApplyCoverage(const GrDrawTargetCaps& caps) const {
361 if (caps.dualSourceBlendingSupport()) {
362 return true;
363 }
364 // we can correctly apply coverage if a) we have dual source blending
365 // or b) one of our blend optimizations applies
366 // or c) the src, dst blend coeffs are 1,0 and we will read Dst Color
367 GrBlendCoeff srcCoeff;
368 GrBlendCoeff dstCoeff;
369 GrDrawState::BlendOptFlags flag = this->getBlendOpts(true, &srcCoeff, &dstCoeff);
370 return GrDrawState::kNone_BlendOpt != flag ||
371 (this->willEffectReadDstColor() &&
372 kOne_GrBlendCoeff == srcCoeff && kZero_GrBlendCoeff == dstCoeff);
373}
374
jvanverth@google.com054ae992013-04-01 20:06:51 +0000375bool GrDrawState::srcAlphaWillBeOne() const {
jvanverth@google.comcc782382013-01-28 20:39:48 +0000376 uint32_t validComponentFlags;
bsalomon@google.com89e6f5b2013-02-27 18:43:47 +0000377 GrColor color;
jvanverth@google.comcc782382013-01-28 20:39:48 +0000378 // Check if per-vertex or constant color may have partial alpha
jvanverth@google.com054ae992013-04-01 20:06:51 +0000379 if (this->hasColorVertexAttribute()) {
bsalomon62c447d2014-08-08 08:08:50 -0700380 if (fHints & kVertexColorsAreOpaque_Hint) {
381 validComponentFlags = kA_GrColorComponentFlag;
382 color = 0xFF << GrColor_SHIFT_A;
383 } else {
384 validComponentFlags = 0;
385 color = 0; // not strictly necessary but we get false alarms from tools about uninit.
386 }
jvanverth@google.comcc782382013-01-28 20:39:48 +0000387 } else {
bsalomon@google.comb8eb2e82013-03-28 13:46:42 +0000388 validComponentFlags = kRGBA_GrColorComponentFlags;
jvanverth@google.comcc782382013-01-28 20:39:48 +0000389 color = this->getColor();
390 }
391
392 // Run through the color stages
egdaniel776bdbd2014-08-06 11:07:02 -0700393 for (int s = 0; s < this->numColorStages(); ++s) {
394 const GrEffect* effect = this->getColorStage(s).getEffect();
bsalomonf99f8842014-07-07 11:54:23 -0700395 effect->getConstantColorComponents(&color, &validComponentFlags);
jvanverth@google.comcc782382013-01-28 20:39:48 +0000396 }
397
jvanverth@google.comcc782382013-01-28 20:39:48 +0000398 // Check whether coverage is treated as color. If so we run through the coverage computation.
399 if (this->isCoverageDrawing()) {
bsalomon637e57e2014-08-18 11:49:37 -0700400 // The shader generated for coverage drawing runs the full coverage computation and then
401 // makes the shader output be the multiplication of color and coverage. We mirror that here.
402 GrColor coverage;
403 uint32_t coverageComponentFlags;
404 if (this->hasCoverageVertexAttribute()) {
405 coverageComponentFlags = 0;
406 coverage = 0; // suppresses any warnings.
407 } else {
408 coverageComponentFlags = kRGBA_GrColorComponentFlags;
409 coverage = this->getCoverageColor();
jvanverth@google.comcc782382013-01-28 20:39:48 +0000410 }
bsalomon637e57e2014-08-18 11:49:37 -0700411
412 // Run through the coverage stages
egdaniel776bdbd2014-08-06 11:07:02 -0700413 for (int s = 0; s < this->numCoverageStages(); ++s) {
414 const GrEffect* effect = this->getCoverageStage(s).getEffect();
bsalomon637e57e2014-08-18 11:49:37 -0700415 effect->getConstantColorComponents(&coverage, &coverageComponentFlags);
jvanverth@google.comcc782382013-01-28 20:39:48 +0000416 }
bsalomon637e57e2014-08-18 11:49:37 -0700417
418 // Since the shader will multiply coverage and color, the only way the final A==1 is if
419 // coverage and color both have A==1.
420 return (kA_GrColorComponentFlag & validComponentFlags & coverageComponentFlags) &&
421 0xFF == GrColorUnpackA(color) && 0xFF == GrColorUnpackA(coverage);
422
jvanverth@google.comcc782382013-01-28 20:39:48 +0000423 }
bsalomon637e57e2014-08-18 11:49:37 -0700424
425 return (kA_GrColorComponentFlag & validComponentFlags) && 0xFF == GrColorUnpackA(color);
jvanverth@google.comcc782382013-01-28 20:39:48 +0000426}
427
jvanverth@google.com054ae992013-04-01 20:06:51 +0000428bool GrDrawState::hasSolidCoverage() const {
bsalomon@google.comd62e88e2013-02-01 14:19:27 +0000429 // If we're drawing coverage directly then coverage is effectively treated as color.
430 if (this->isCoverageDrawing()) {
431 return true;
432 }
433
434 GrColor coverage;
435 uint32_t validComponentFlags;
436 // Initialize to an unknown starting coverage if per-vertex coverage is specified.
jvanverth@google.com054ae992013-04-01 20:06:51 +0000437 if (this->hasCoverageVertexAttribute()) {
bsalomon@google.comd62e88e2013-02-01 14:19:27 +0000438 validComponentFlags = 0;
439 } else {
egdaniel8cbf3d52014-08-21 06:27:22 -0700440 coverage = this->getCoverageColor();
bsalomon@google.comb8eb2e82013-03-28 13:46:42 +0000441 validComponentFlags = kRGBA_GrColorComponentFlags;
bsalomon@google.comd62e88e2013-02-01 14:19:27 +0000442 }
443
444 // Run through the coverage stages and see if the coverage will be all ones at the end.
egdaniel776bdbd2014-08-06 11:07:02 -0700445 for (int s = 0; s < this->numCoverageStages(); ++s) {
446 const GrEffect* effect = this->getCoverageStage(s).getEffect();
bsalomonf99f8842014-07-07 11:54:23 -0700447 effect->getConstantColorComponents(&coverage, &validComponentFlags);
bsalomon@google.comd62e88e2013-02-01 14:19:27 +0000448 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000449 return (kRGBA_GrColorComponentFlags == validComponentFlags) && (0xffffffff == coverage);
bsalomon@google.comd62e88e2013-02-01 14:19:27 +0000450}
451
jvanverth@google.comcc782382013-01-28 20:39:48 +0000452////////////////////////////////////////////////////////////////////////////////
453
bsalomon@google.com2b446732013-02-12 16:47:41 +0000454// Some blend modes allow folding a fractional coverage value into the color's alpha channel, while
455// others will blend incorrectly.
456bool GrDrawState::canTweakAlphaForCoverage() const {
457 /*
458 The fractional coverage is f.
459 The src and dst coeffs are Cs and Cd.
460 The dst and src colors are S and D.
461 We want the blend to compute: f*Cs*S + (f*Cd + (1-f))D. By tweaking the source color's alpha
462 we're replacing S with S'=fS. It's obvious that that first term will always be ok. The second
463 term can be rearranged as [1-(1-Cd)f]D. By substituting in the various possibilities for Cd we
464 find that only 1, ISA, and ISC produce the correct destination when applied to S' and D.
465 Also, if we're directly rendering coverage (isCoverageDrawing) then coverage is treated as
466 color by definition.
467 */
egdaniel8cbf3d52014-08-21 06:27:22 -0700468 return kOne_GrBlendCoeff == fDstBlend ||
469 kISA_GrBlendCoeff == fDstBlend ||
470 kISC_GrBlendCoeff == fDstBlend ||
bsalomon@google.com2b446732013-02-12 16:47:41 +0000471 this->isCoverageDrawing();
472}
473
474GrDrawState::BlendOptFlags GrDrawState::getBlendOpts(bool forceCoverage,
475 GrBlendCoeff* srcCoeff,
476 GrBlendCoeff* dstCoeff) const {
bsalomon@google.com2b446732013-02-12 16:47:41 +0000477 GrBlendCoeff bogusSrcCoeff, bogusDstCoeff;
478 if (NULL == srcCoeff) {
479 srcCoeff = &bogusSrcCoeff;
480 }
bsalomon@google.com2b446732013-02-12 16:47:41 +0000481 if (NULL == dstCoeff) {
482 dstCoeff = &bogusDstCoeff;
483 }
egdaniel9514d242014-07-18 06:15:43 -0700484
485 if (forceCoverage) {
486 return this->calcBlendOpts(true, srcCoeff, dstCoeff);
487 }
488
489 if (0 == (fBlendOptFlags & kInvalid_BlendOptFlag)) {
490 *srcCoeff = fOptSrcBlend;
491 *dstCoeff = fOptDstBlend;
492 return fBlendOptFlags;
493 }
494
495 fBlendOptFlags = this->calcBlendOpts(forceCoverage, srcCoeff, dstCoeff);
496 fOptSrcBlend = *srcCoeff;
497 fOptDstBlend = *dstCoeff;
498
499 return fBlendOptFlags;
500}
501
502GrDrawState::BlendOptFlags GrDrawState::calcBlendOpts(bool forceCoverage,
503 GrBlendCoeff* srcCoeff,
504 GrBlendCoeff* dstCoeff) const {
505 *srcCoeff = this->getSrcBlendCoeff();
bsalomon@google.com2b446732013-02-12 16:47:41 +0000506 *dstCoeff = this->getDstBlendCoeff();
507
508 if (this->isColorWriteDisabled()) {
509 *srcCoeff = kZero_GrBlendCoeff;
510 *dstCoeff = kOne_GrBlendCoeff;
511 }
512
jvanverth@google.com054ae992013-04-01 20:06:51 +0000513 bool srcAIsOne = this->srcAlphaWillBeOne();
bsalomon@google.com2b446732013-02-12 16:47:41 +0000514 bool dstCoeffIsOne = kOne_GrBlendCoeff == *dstCoeff ||
515 (kSA_GrBlendCoeff == *dstCoeff && srcAIsOne);
516 bool dstCoeffIsZero = kZero_GrBlendCoeff == *dstCoeff ||
517 (kISA_GrBlendCoeff == *dstCoeff && srcAIsOne);
518
bsalomon@google.com2b446732013-02-12 16:47:41 +0000519 // When coeffs are (0,1) there is no reason to draw at all, unless
520 // stenciling is enabled. Having color writes disabled is effectively
bsalomon62c447d2014-08-08 08:08:50 -0700521 // (0,1).
522 if ((kZero_GrBlendCoeff == *srcCoeff && dstCoeffIsOne)) {
bsalomon@google.com2b446732013-02-12 16:47:41 +0000523 if (this->getStencil().doesWrite()) {
egdaniel0f1a7c42014-07-30 13:18:32 -0700524 return kEmitCoverage_BlendOptFlag;
bsalomon@google.com2b446732013-02-12 16:47:41 +0000525 } else {
526 return kSkipDraw_BlendOptFlag;
527 }
528 }
529
bsalomon62c447d2014-08-08 08:08:50 -0700530 bool hasCoverage = forceCoverage || !this->hasSolidCoverage();
bsalomon@google.com2b446732013-02-12 16:47:41 +0000531
532 // if we don't have coverage we can check whether the dst
533 // has to read at all. If not, we'll disable blending.
534 if (!hasCoverage) {
535 if (dstCoeffIsZero) {
536 if (kOne_GrBlendCoeff == *srcCoeff) {
537 // if there is no coverage and coeffs are (1,0) then we
538 // won't need to read the dst at all, it gets replaced by src
egdaniel0f1a7c42014-07-30 13:18:32 -0700539 *dstCoeff = kZero_GrBlendCoeff;
540 return kNone_BlendOpt;
bsalomon@google.com2b446732013-02-12 16:47:41 +0000541 } else if (kZero_GrBlendCoeff == *srcCoeff) {
542 // if the op is "clear" then we don't need to emit a color
543 // or blend, just write transparent black into the dst.
544 *srcCoeff = kOne_GrBlendCoeff;
545 *dstCoeff = kZero_GrBlendCoeff;
egdaniel0f1a7c42014-07-30 13:18:32 -0700546 return kEmitTransBlack_BlendOptFlag;
bsalomon@google.com2b446732013-02-12 16:47:41 +0000547 }
548 }
549 } else if (this->isCoverageDrawing()) {
550 // we have coverage but we aren't distinguishing it from alpha by request.
551 return kCoverageAsAlpha_BlendOptFlag;
552 } else {
553 // check whether coverage can be safely rolled into alpha
554 // of if we can skip color computation and just emit coverage
555 if (this->canTweakAlphaForCoverage()) {
556 return kCoverageAsAlpha_BlendOptFlag;
557 }
558 if (dstCoeffIsZero) {
559 if (kZero_GrBlendCoeff == *srcCoeff) {
560 // the source color is not included in the blend
561 // the dst coeff is effectively zero so blend works out to:
562 // (c)(0)D + (1-c)D = (1-c)D.
563 *dstCoeff = kISA_GrBlendCoeff;
564 return kEmitCoverage_BlendOptFlag;
565 } else if (srcAIsOne) {
566 // the dst coeff is effectively zero so blend works out to:
567 // cS + (c)(0)D + (1-c)D = cS + (1-c)D.
568 // If Sa is 1 then we can replace Sa with c
569 // and set dst coeff to 1-Sa.
570 *dstCoeff = kISA_GrBlendCoeff;
571 return kCoverageAsAlpha_BlendOptFlag;
572 }
573 } else if (dstCoeffIsOne) {
574 // the dst coeff is effectively one so blend works out to:
575 // cS + (c)(1)D + (1-c)D = cS + D.
576 *dstCoeff = kOne_GrBlendCoeff;
577 return kCoverageAsAlpha_BlendOptFlag;
578 }
579 }
egdaniel0f1a7c42014-07-30 13:18:32 -0700580
bsalomon@google.com2b446732013-02-12 16:47:41 +0000581 return kNone_BlendOpt;
582}
583
egdaniel02cafcc2014-07-21 11:37:28 -0700584bool GrDrawState::canIgnoreColorAttribute() const {
585 if (fBlendOptFlags & kInvalid_BlendOptFlag) {
586 this->getBlendOpts();
587 }
588 return SkToBool(fBlendOptFlags & (GrDrawState::kEmitTransBlack_BlendOptFlag |
589 GrDrawState::kEmitCoverage_BlendOptFlag));
590}
591
bsalomon8f727332014-08-05 07:50:06 -0700592//////////////////////////////////////////////////////////////////////////////
593
594GrDrawState::AutoVertexAttribRestore::AutoVertexAttribRestore(
595 GrDrawState* drawState) {
596 SkASSERT(NULL != drawState);
597 fDrawState = drawState;
598 fVAPtr = drawState->fVAPtr;
599 fVACount = drawState->fVACount;
600 fDrawState->setDefaultVertexAttribs();
601}
602
603//////////////////////////////////////////////////////////////////////////////s
604
605void GrDrawState::AutoRestoreEffects::set(GrDrawState* ds) {
606 if (NULL != fDrawState) {
egdaniel776bdbd2014-08-06 11:07:02 -0700607 int m = fDrawState->numColorStages() - fColorEffectCnt;
bsalomon8f727332014-08-05 07:50:06 -0700608 SkASSERT(m >= 0);
egdaniel8cbf3d52014-08-21 06:27:22 -0700609 fDrawState->fColorStages.pop_back_n(m);
bsalomon8f727332014-08-05 07:50:06 -0700610
egdaniel776bdbd2014-08-06 11:07:02 -0700611 int n = fDrawState->numCoverageStages() - fCoverageEffectCnt;
bsalomon8f727332014-08-05 07:50:06 -0700612 SkASSERT(n >= 0);
egdaniel8cbf3d52014-08-21 06:27:22 -0700613 fDrawState->fCoverageStages.pop_back_n(n);
bsalomon8f727332014-08-05 07:50:06 -0700614 if (m + n > 0) {
615 fDrawState->invalidateBlendOptFlags();
616 }
617 SkDEBUGCODE(--fDrawState->fBlockEffectRemovalCnt;)
618 }
619 fDrawState = ds;
620 if (NULL != ds) {
egdaniel776bdbd2014-08-06 11:07:02 -0700621 fColorEffectCnt = ds->numColorStages();
622 fCoverageEffectCnt = ds->numCoverageStages();
bsalomon8f727332014-08-05 07:50:06 -0700623 SkDEBUGCODE(++ds->fBlockEffectRemovalCnt;)
624 }
625}
egdaniel02cafcc2014-07-21 11:37:28 -0700626
bsalomon@google.com2b446732013-02-12 16:47:41 +0000627////////////////////////////////////////////////////////////////////////////////
628
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000629void GrDrawState::AutoViewMatrixRestore::restore() {
630 if (NULL != fDrawState) {
commit-bot@chromium.org1acc3d72013-09-06 23:13:05 +0000631 SkDEBUGCODE(--fDrawState->fBlockEffectRemovalCnt;)
bsalomon2ed5ef82014-07-07 08:44:05 -0700632 fDrawState->fViewMatrix = fViewMatrix;
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000633 SkASSERT(fDrawState->numColorStages() >= fNumColorStages);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000634 int numCoverageStages = fSavedCoordChanges.count() - fNumColorStages;
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000635 SkASSERT(fDrawState->numCoverageStages() >= numCoverageStages);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000636
637 int i = 0;
638 for (int s = 0; s < fNumColorStages; ++s, ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700639 fDrawState->fColorStages[s].restoreCoordChange(fSavedCoordChanges[i]);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000640 }
641 for (int s = 0; s < numCoverageStages; ++s, ++i) {
egdaniel8cbf3d52014-08-21 06:27:22 -0700642 fDrawState->fCoverageStages[s].restoreCoordChange(fSavedCoordChanges[i]);
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000643 }
bsalomon@google.com137f1342013-05-29 21:27:53 +0000644 fDrawState = NULL;
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000645 }
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000646}
647
648void GrDrawState::AutoViewMatrixRestore::set(GrDrawState* drawState,
bsalomon@google.comc7818882013-03-20 19:19:53 +0000649 const SkMatrix& preconcatMatrix) {
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000650 this->restore();
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000651
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000652 SkASSERT(NULL == fDrawState);
bsalomon@google.com137f1342013-05-29 21:27:53 +0000653 if (NULL == drawState || preconcatMatrix.isIdentity()) {
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000654 return;
655 }
bsalomon@google.com137f1342013-05-29 21:27:53 +0000656 fDrawState = drawState;
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000657
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000658 fViewMatrix = drawState->getViewMatrix();
bsalomon2ed5ef82014-07-07 08:44:05 -0700659 drawState->fViewMatrix.preConcat(preconcatMatrix);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000660
661 this->doEffectCoordChanges(preconcatMatrix);
commit-bot@chromium.org1acc3d72013-09-06 23:13:05 +0000662 SkDEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000663}
664
bsalomon@google.com137f1342013-05-29 21:27:53 +0000665bool GrDrawState::AutoViewMatrixRestore::setIdentity(GrDrawState* drawState) {
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000666 this->restore();
667
bsalomon@google.com137f1342013-05-29 21:27:53 +0000668 if (NULL == drawState) {
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000669 return false;
skia.committer@gmail.comf467ce72012-10-09 02:01:37 +0000670 }
bsalomon@google.com2fdcdeb2012-10-08 17:15:55 +0000671
bsalomon@google.com137f1342013-05-29 21:27:53 +0000672 if (drawState->getViewMatrix().isIdentity()) {
673 return true;
674 }
675
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000676 fViewMatrix = drawState->getViewMatrix();
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000677 if (0 == drawState->numTotalStages()) {
bsalomon2ed5ef82014-07-07 08:44:05 -0700678 drawState->fViewMatrix.reset();
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000679 fDrawState = drawState;
680 fNumColorStages = 0;
681 fSavedCoordChanges.reset(0);
commit-bot@chromium.org1acc3d72013-09-06 23:13:05 +0000682 SkDEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000683 return true;
684 } else {
685 SkMatrix inv;
686 if (!fViewMatrix.invert(&inv)) {
687 return false;
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000688 }
bsalomon2ed5ef82014-07-07 08:44:05 -0700689 drawState->fViewMatrix.reset();
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000690 fDrawState = drawState;
691 this->doEffectCoordChanges(inv);
commit-bot@chromium.org1acc3d72013-09-06 23:13:05 +0000692 SkDEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000693 return true;
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000694 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000695}
696
697void GrDrawState::AutoViewMatrixRestore::doEffectCoordChanges(const SkMatrix& coordChangeMatrix) {
698 fSavedCoordChanges.reset(fDrawState->numTotalStages());
699 int i = 0;
700
701 fNumColorStages = fDrawState->numColorStages();
702 for (int s = 0; s < fNumColorStages; ++s, ++i) {
egdaniel776bdbd2014-08-06 11:07:02 -0700703 fDrawState->getColorStage(s).saveCoordChange(&fSavedCoordChanges[i]);
egdaniel8cbf3d52014-08-21 06:27:22 -0700704 fDrawState->fColorStages[s].localCoordChange(coordChangeMatrix);
bsalomon@google.com137f1342013-05-29 21:27:53 +0000705 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000706
707 int numCoverageStages = fDrawState->numCoverageStages();
708 for (int s = 0; s < numCoverageStages; ++s, ++i) {
egdaniel776bdbd2014-08-06 11:07:02 -0700709 fDrawState->getCoverageStage(s).saveCoordChange(&fSavedCoordChanges[i]);
egdaniel8cbf3d52014-08-21 06:27:22 -0700710 fDrawState->fCoverageStages[s].localCoordChange(coordChangeMatrix);
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000711 }
bsalomon@google.com5b3e8902012-10-05 20:13:28 +0000712}