blob: 77a9fc8dc454a25672d849f9ec8d5fb1f10c2081 [file] [log] [blame]
egdaniel3658f382014-09-15 07:01:59 -07001/*
2 * Copyright 2014 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 "GrOptDrawState.h"
9
10#include "GrDrawState.h"
egdanielc0648242014-09-22 13:17:02 -070011#include "GrDrawTargetCaps.h"
egdaniel3658f382014-09-15 07:01:59 -070012
egdaniel170f90b2014-09-16 12:54:40 -070013GrOptDrawState::GrOptDrawState(const GrDrawState& drawState,
14 BlendOptFlags blendOptFlags,
15 GrBlendCoeff optSrcCoeff,
egdanielc0648242014-09-22 13:17:02 -070016 GrBlendCoeff optDstCoeff,
egdaniel89af44a2014-09-26 06:15:04 -070017 const GrDrawTargetCaps& caps) {
bsalomonbcf0a522014-10-08 08:40:09 -070018 fRenderTarget.set(SkSafeRef(drawState.getRenderTarget()), kWrite_GrIOType);
egdaniel3658f382014-09-15 07:01:59 -070019 fColor = drawState.getColor();
20 fCoverage = drawState.getCoverage();
21 fViewMatrix = drawState.getViewMatrix();
22 fBlendConstant = drawState.getBlendConstant();
23 fFlagBits = drawState.getFlagBits();
24 fVAPtr = drawState.getVertexAttribs();
25 fVACount = drawState.getVertexAttribCount();
26 fVAStride = drawState.getVertexStride();
27 fStencilSettings = drawState.getStencil();
egdaniel89af44a2014-09-26 06:15:04 -070028 fDrawFace = (DrawFace)drawState.getDrawFace();
egdaniel170f90b2014-09-16 12:54:40 -070029 fBlendOptFlags = blendOptFlags;
30 fSrcBlend = optSrcCoeff;
31 fDstBlend = optDstCoeff;
egdaniel3658f382014-09-15 07:01:59 -070032
33 memcpy(fFixedFunctionVertexAttribIndices,
egdaniel89af44a2014-09-26 06:15:04 -070034 drawState.getFixedFunctionVertexAttribIndices(),
35 sizeof(fFixedFunctionVertexAttribIndices));
egdaniel3658f382014-09-15 07:01:59 -070036
37 fInputColorIsUsed = true;
38 fInputCoverageIsUsed = true;
39
egdaniel9cf45bf2014-10-08 06:49:10 -070040 int firstColorStageIdx = 0;
41 int firstCoverageStageIdx = 0;
42 bool separateCoverageFromColor;
43
44 uint8_t fixedFunctionVAToRemove = 0;
45
46 this->computeEffectiveColorStages(drawState, &firstColorStageIdx, &fixedFunctionVAToRemove);
47 this->computeEffectiveCoverageStages(drawState, &firstCoverageStageIdx);
48 this->adjustFromBlendOpts(drawState, &firstColorStageIdx, &firstCoverageStageIdx,
49 &fixedFunctionVAToRemove);
50 // Should not be setting any more FFVA to be removed at this point
egdanielc0651c12014-10-21 07:47:10 -070051 if (0 != fixedFunctionVAToRemove) {
52 this->removeFixedFunctionVertexAttribs(fixedFunctionVAToRemove);
53 }
egdaniel9cf45bf2014-10-08 06:49:10 -070054 this->getStageStats(drawState, firstColorStageIdx, firstCoverageStageIdx);
55 this->setOutputStateInfo(drawState, caps, firstCoverageStageIdx, &separateCoverageFromColor);
56
57 // Copy GeometryProcesssor from DS or ODS
egdaniel3658f382014-09-15 07:01:59 -070058 if (drawState.hasGeometryProcessor()) {
joshualitta5305a12014-10-10 17:47:00 -070059 fGeometryProcessor.initAndRef(drawState.fGeometryProcessor);
egdaniel3658f382014-09-15 07:01:59 -070060 } else {
61 fGeometryProcessor.reset(NULL);
62 }
63
egdaniel9cf45bf2014-10-08 06:49:10 -070064 // Copy Color Stages from DS to ODS
65 if (firstColorStageIdx < drawState.numColorStages()) {
egdanield9aa2182014-10-09 13:47:05 -070066 fFragmentStages.reset(&drawState.getColorStage(firstColorStageIdx),
67 drawState.numColorStages() - firstColorStageIdx);
egdaniel9cf45bf2014-10-08 06:49:10 -070068 } else {
egdanield9aa2182014-10-09 13:47:05 -070069 fFragmentStages.reset();
egdaniel9cf45bf2014-10-08 06:49:10 -070070 }
71
egdanield9aa2182014-10-09 13:47:05 -070072 fNumColorStages = fFragmentStages.count();
73
egdaniel9cf45bf2014-10-08 06:49:10 -070074 // Copy Coverage Stages from DS to ODS
egdanield9aa2182014-10-09 13:47:05 -070075 if (firstCoverageStageIdx < drawState.numCoverageStages()) {
76 fFragmentStages.push_back_n(drawState.numCoverageStages() - firstCoverageStageIdx,
77 &drawState.getCoverageStage(firstCoverageStageIdx));
78 if (!separateCoverageFromColor) {
79 fNumColorStages = fFragmentStages.count();
egdaniel9cf45bf2014-10-08 06:49:10 -070080 }
81 }
egdaniel3658f382014-09-15 07:01:59 -070082};
83
egdanielb109ac22014-10-07 06:45:44 -070084GrOptDrawState* GrOptDrawState::Create(const GrDrawState& drawState, const GrDrawTargetCaps& caps,
85 GrGpu::DrawType drawType) {
86 if (NULL == drawState.fCachedOptState || caps.getUniqueID() != drawState.fCachedCapsID) {
87 GrBlendCoeff srcCoeff;
88 GrBlendCoeff dstCoeff;
89 BlendOptFlags blendFlags = (BlendOptFlags) drawState.getBlendOpts(false,
90 &srcCoeff,
91 &dstCoeff);
92
93 // If our blend coeffs are set to 0,1 we know we will not end up drawing unless we are
94 // stenciling. When path rendering the stencil settings are not always set on the draw state
95 // so we must check the draw type. In cases where we will skip drawing we simply return a
96 // null GrOptDrawState.
97 if (kZero_GrBlendCoeff == srcCoeff && kOne_GrBlendCoeff == dstCoeff &&
98 !drawState.getStencil().doesWrite() && GrGpu::kStencilPath_DrawType != drawType) {
99 return NULL;
100 }
101
102 drawState.fCachedOptState = SkNEW_ARGS(GrOptDrawState, (drawState, blendFlags, srcCoeff,
103 dstCoeff, caps));
104 drawState.fCachedCapsID = caps.getUniqueID();
105 } else {
106#ifdef SK_DEBUG
107 GrBlendCoeff srcCoeff;
108 GrBlendCoeff dstCoeff;
109 BlendOptFlags blendFlags = (BlendOptFlags) drawState.getBlendOpts(false,
110 &srcCoeff,
111 &dstCoeff);
112 SkASSERT(GrOptDrawState(drawState, blendFlags, srcCoeff, dstCoeff, caps) ==
113 *drawState.fCachedOptState);
114#endif
115 }
116 drawState.fCachedOptState->ref();
117 return drawState.fCachedOptState;
118}
119
egdaniel9cf45bf2014-10-08 06:49:10 -0700120void GrOptDrawState::setOutputStateInfo(const GrDrawState& ds,
121 const GrDrawTargetCaps& caps,
122 int firstCoverageStageIdx,
123 bool* separateCoverageFromColor) {
egdanielc0648242014-09-22 13:17:02 -0700124 // Set this default and then possibly change our mind if there is coverage.
125 fPrimaryOutputType = kModulate_PrimaryOutputType;
126 fSecondaryOutputType = kNone_SecondaryOutputType;
127
128 // If we do have coverage determine whether it matters.
egdaniel9cf45bf2014-10-08 06:49:10 -0700129 *separateCoverageFromColor = this->hasGeometryProcessor();
egdanielc0648242014-09-22 13:17:02 -0700130 if (!this->isCoverageDrawing() &&
egdaniel9cf45bf2014-10-08 06:49:10 -0700131 (ds.numCoverageStages() - firstCoverageStageIdx > 0 ||
132 ds.hasGeometryProcessor() ||
egdanielc0648242014-09-22 13:17:02 -0700133 this->hasCoverageVertexAttribute())) {
134
135 if (caps.dualSourceBlendingSupport()) {
136 if (kZero_GrBlendCoeff == fDstBlend) {
137 // write the coverage value to second color
138 fSecondaryOutputType = kCoverage_SecondaryOutputType;
egdaniel9cf45bf2014-10-08 06:49:10 -0700139 *separateCoverageFromColor = true;
egdanielc0648242014-09-22 13:17:02 -0700140 fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
141 } else if (kSA_GrBlendCoeff == fDstBlend) {
142 // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
143 fSecondaryOutputType = kCoverageISA_SecondaryOutputType;
egdaniel9cf45bf2014-10-08 06:49:10 -0700144 *separateCoverageFromColor = true;
egdanielc0648242014-09-22 13:17:02 -0700145 fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
146 } else if (kSC_GrBlendCoeff == fDstBlend) {
147 // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
148 fSecondaryOutputType = kCoverageISC_SecondaryOutputType;
egdaniel9cf45bf2014-10-08 06:49:10 -0700149 *separateCoverageFromColor = true;
egdanielc0648242014-09-22 13:17:02 -0700150 fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
151 }
152 } else if (fReadsDst &&
153 kOne_GrBlendCoeff == fSrcBlend &&
154 kZero_GrBlendCoeff == fDstBlend) {
155 fPrimaryOutputType = kCombineWithDst_PrimaryOutputType;
egdaniel9cf45bf2014-10-08 06:49:10 -0700156 *separateCoverageFromColor = true;
egdanielc0648242014-09-22 13:17:02 -0700157 }
158 }
egdanielc0648242014-09-22 13:17:02 -0700159}
160
egdaniel9cf45bf2014-10-08 06:49:10 -0700161void GrOptDrawState::adjustFromBlendOpts(const GrDrawState& ds,
162 int* firstColorStageIdx,
163 int* firstCoverageStageIdx,
164 uint8_t* fixedFunctionVAToRemove) {
egdaniel170f90b2014-09-16 12:54:40 -0700165 switch (fBlendOptFlags) {
166 case kNone_BlendOpt:
167 case kSkipDraw_BlendOptFlag:
168 break;
169 case kCoverageAsAlpha_BlendOptFlag:
170 fFlagBits |= kCoverageDrawing_StateBit;
171 break;
172 case kEmitCoverage_BlendOptFlag:
173 fColor = 0xffffffff;
174 fInputColorIsUsed = true;
egdaniel9cf45bf2014-10-08 06:49:10 -0700175 *firstColorStageIdx = ds.numColorStages();
176 *fixedFunctionVAToRemove |= 0x1 << kColor_GrVertexAttribBinding;
egdaniel170f90b2014-09-16 12:54:40 -0700177 break;
178 case kEmitTransBlack_BlendOptFlag:
179 fColor = 0;
180 fCoverage = 0xff;
181 fInputColorIsUsed = true;
182 fInputCoverageIsUsed = true;
egdaniel9cf45bf2014-10-08 06:49:10 -0700183 *firstColorStageIdx = ds.numColorStages();
184 *firstCoverageStageIdx = ds.numCoverageStages();
185 *fixedFunctionVAToRemove |= (0x1 << kColor_GrVertexAttribBinding |
186 0x1 << kCoverage_GrVertexAttribBinding);
egdaniel170f90b2014-09-16 12:54:40 -0700187 break;
188 default:
189 SkFAIL("Unknown BlendOptFlag");
egdaniel170f90b2014-09-16 12:54:40 -0700190 }
191}
192
egdaniel3658f382014-09-15 07:01:59 -0700193void GrOptDrawState::removeFixedFunctionVertexAttribs(uint8_t removeVAFlag) {
194 int numToRemove = 0;
195 uint8_t maskCheck = 0x1;
196 // Count the number of vertex attributes that we will actually remove
197 for (int i = 0; i < kGrFixedFunctionVertexAttribBindingCnt; ++i) {
198 if ((maskCheck & removeVAFlag) && -1 != fFixedFunctionVertexAttribIndices[i]) {
199 ++numToRemove;
200 }
201 maskCheck <<= 1;
202 }
egdaniel170f90b2014-09-16 12:54:40 -0700203
egdaniel3658f382014-09-15 07:01:59 -0700204 fOptVA.reset(fVACount - numToRemove);
205
206 GrVertexAttrib* dst = fOptVA.get();
207 const GrVertexAttrib* src = fVAPtr;
208
209 for (int i = 0, newIdx = 0; i < fVACount; ++i, ++src) {
210 const GrVertexAttrib& currAttrib = *src;
211 if (currAttrib.fBinding < kGrFixedFunctionVertexAttribBindingCnt) {
212 uint8_t maskCheck = 0x1 << currAttrib.fBinding;
213 if (maskCheck & removeVAFlag) {
214 SkASSERT(-1 != fFixedFunctionVertexAttribIndices[currAttrib.fBinding]);
215 fFixedFunctionVertexAttribIndices[currAttrib.fBinding] = -1;
216 continue;
217 }
egdaniel170f90b2014-09-16 12:54:40 -0700218 fFixedFunctionVertexAttribIndices[currAttrib.fBinding] = newIdx;
egdaniel3658f382014-09-15 07:01:59 -0700219 }
220 memcpy(dst, src, sizeof(GrVertexAttrib));
egdaniel3658f382014-09-15 07:01:59 -0700221 ++newIdx;
222 ++dst;
223 }
224 fVACount -= numToRemove;
225 fVAPtr = fOptVA.get();
226}
227
egdaniel9cf45bf2014-10-08 06:49:10 -0700228void GrOptDrawState::computeEffectiveColorStages(const GrDrawState& ds, int* firstColorStageIdx,
229 uint8_t* fixedFunctionVAToRemove) {
egdaniel3658f382014-09-15 07:01:59 -0700230 // Set up color and flags for ConstantColorComponent checks
egdaniel1a8ecdf2014-10-03 06:24:12 -0700231 GrProcessor::InvariantOutput inout;
232 inout.fIsSingleComponent = false;
egdaniel3658f382014-09-15 07:01:59 -0700233 if (!this->hasColorVertexAttribute()) {
egdaniel1a8ecdf2014-10-03 06:24:12 -0700234 inout.fColor = ds.getColor();
235 inout.fValidFlags = kRGBA_GrColorComponentFlags;
egdaniel3658f382014-09-15 07:01:59 -0700236 } else {
237 if (ds.vertexColorsAreOpaque()) {
egdaniel1a8ecdf2014-10-03 06:24:12 -0700238 inout.fColor = 0xFF << GrColor_SHIFT_A;
239 inout.fValidFlags = kA_GrColorComponentFlag;
egdaniel3658f382014-09-15 07:01:59 -0700240 } else {
egdaniel1a8ecdf2014-10-03 06:24:12 -0700241 inout.fValidFlags = 0;
242 // not strictly necessary but we get false alarms from tools about uninit.
243 inout.fColor = 0;
egdaniel3658f382014-09-15 07:01:59 -0700244 }
245 }
246
247 for (int i = 0; i < ds.numColorStages(); ++i) {
joshualitt47bb3822014-10-07 16:43:25 -0700248 const GrFragmentProcessor* fp = ds.getColorStage(i).getProcessor();
egdaniel9e4d6d12014-10-15 13:49:02 -0700249 fp->computeInvariantOutput(&inout);
250 if (!inout.fWillUseInputColor) {
egdaniel9cf45bf2014-10-08 06:49:10 -0700251 *firstColorStageIdx = i;
egdaniel3658f382014-09-15 07:01:59 -0700252 fInputColorIsUsed = false;
253 }
egdaniel1a8ecdf2014-10-03 06:24:12 -0700254 if (kRGBA_GrColorComponentFlags == inout.fValidFlags) {
egdaniel9cf45bf2014-10-08 06:49:10 -0700255 *firstColorStageIdx = i + 1;
egdaniel1a8ecdf2014-10-03 06:24:12 -0700256 fColor = inout.fColor;
egdaniel3658f382014-09-15 07:01:59 -0700257 fInputColorIsUsed = true;
egdaniel9cf45bf2014-10-08 06:49:10 -0700258 *fixedFunctionVAToRemove |= 0x1 << kColor_GrVertexAttribBinding;
egdaniel9e4d6d12014-10-15 13:49:02 -0700259 // Since we are clearing all previous color stages we are in a state where we have found
260 // zero stages that don't multiply the inputColor.
261 inout.fNonMulStageFound = false;
egdaniel3658f382014-09-15 07:01:59 -0700262 }
263 }
egdaniel3658f382014-09-15 07:01:59 -0700264}
265
egdaniel9cf45bf2014-10-08 06:49:10 -0700266void GrOptDrawState::computeEffectiveCoverageStages(const GrDrawState& ds,
267 int* firstCoverageStageIdx) {
egdaniel3658f382014-09-15 07:01:59 -0700268 // We do not try to optimize out constantColor coverage effects here. It is extremely rare
269 // to have a coverage effect that returns a constant value for all four channels. Thus we
270 // save having to make extra virtual calls by not checking for it.
271
272 // Don't do any optimizations on coverage stages. It should not be the case where we do not use
273 // input coverage in an effect
274#ifdef OptCoverageStages
egdaniel9e4d6d12014-10-15 13:49:02 -0700275 GrProcessor::InvariantOutput inout;
egdaniel3658f382014-09-15 07:01:59 -0700276 for (int i = 0; i < ds.numCoverageStages(); ++i) {
egdaniel9e4d6d12014-10-15 13:49:02 -0700277 const GrFragmentProcessor* fp = ds.getCoverageStage(i).getProcessor();
278 fp->computeInvariantOutput(&inout);
279 if (!inout.fWillUseInputColor) {
egdaniel9cf45bf2014-10-08 06:49:10 -0700280 *firstCoverageStageIdx = i;
egdaniel3658f382014-09-15 07:01:59 -0700281 fInputCoverageIsUsed = false;
282 }
283 }
284#endif
egdaniel3658f382014-09-15 07:01:59 -0700285}
286
joshualittb0a8a372014-09-23 09:50:21 -0700287static void get_stage_stats(const GrFragmentStage& stage, bool* readsDst, bool* readsFragPosition) {
joshualitt47bb3822014-10-07 16:43:25 -0700288 if (stage.getProcessor()->willReadDstColor()) {
egdaniela7dc0a82014-09-17 08:25:05 -0700289 *readsDst = true;
290 }
joshualitt47bb3822014-10-07 16:43:25 -0700291 if (stage.getProcessor()->willReadFragmentPosition()) {
egdaniela7dc0a82014-09-17 08:25:05 -0700292 *readsFragPosition = true;
293 }
294}
joshualittb0a8a372014-09-23 09:50:21 -0700295
egdaniel9cf45bf2014-10-08 06:49:10 -0700296void GrOptDrawState::getStageStats(const GrDrawState& ds, int firstColorStageIdx,
297 int firstCoverageStageIdx) {
egdaniela7dc0a82014-09-17 08:25:05 -0700298 // We will need a local coord attrib if there is one currently set on the optState and we are
299 // actually generating some effect code
egdaniel9cf45bf2014-10-08 06:49:10 -0700300 fRequiresLocalCoordAttrib = this->hasLocalCoordAttribute() &&
301 ds.numTotalStages() - firstColorStageIdx - firstCoverageStageIdx > 0;
egdaniela7dc0a82014-09-17 08:25:05 -0700302
egdaniela7dc0a82014-09-17 08:25:05 -0700303 fReadsDst = false;
304 fReadsFragPosition = false;
305
egdaniel9cf45bf2014-10-08 06:49:10 -0700306 for (int s = firstColorStageIdx; s < ds.numColorStages(); ++s) {
307 const GrFragmentStage& stage = ds.getColorStage(s);
egdaniela7dc0a82014-09-17 08:25:05 -0700308 get_stage_stats(stage, &fReadsDst, &fReadsFragPosition);
309 }
egdaniel9cf45bf2014-10-08 06:49:10 -0700310 for (int s = firstCoverageStageIdx; s < ds.numCoverageStages(); ++s) {
311 const GrFragmentStage& stage = ds.getCoverageStage(s);
egdaniela7dc0a82014-09-17 08:25:05 -0700312 get_stage_stats(stage, &fReadsDst, &fReadsFragPosition);
313 }
egdaniel9cf45bf2014-10-08 06:49:10 -0700314 if (ds.hasGeometryProcessor()) {
joshualitta5305a12014-10-10 17:47:00 -0700315 const GrGeometryProcessor& gp = *ds.getGeometryProcessor();
316 fReadsFragPosition = fReadsFragPosition || gp.willReadFragmentPosition();
egdaniela7dc0a82014-09-17 08:25:05 -0700317 }
318}
319
egdaniel89af44a2014-09-26 06:15:04 -0700320////////////////////////////////////////////////////////////////////////////////
321
egdaniel3658f382014-09-15 07:01:59 -0700322bool GrOptDrawState::operator== (const GrOptDrawState& that) const {
323 return this->isEqual(that);
324}
325
egdaniel89af44a2014-09-26 06:15:04 -0700326bool GrOptDrawState::isEqual(const GrOptDrawState& that) const {
327 bool usingVertexColors = this->hasColorVertexAttribute();
328 if (!usingVertexColors && this->fColor != that.fColor) {
329 return false;
330 }
331
332 if (this->getRenderTarget() != that.getRenderTarget() ||
egdanield9aa2182014-10-09 13:47:05 -0700333 this->fFragmentStages.count() != that.fFragmentStages.count() ||
334 this->fNumColorStages != that.fNumColorStages ||
egdaniel89af44a2014-09-26 06:15:04 -0700335 !this->fViewMatrix.cheapEqualTo(that.fViewMatrix) ||
336 this->fSrcBlend != that.fSrcBlend ||
337 this->fDstBlend != that.fDstBlend ||
338 this->fBlendConstant != that.fBlendConstant ||
339 this->fFlagBits != that.fFlagBits ||
340 this->fVACount != that.fVACount ||
341 this->fVAStride != that.fVAStride ||
342 memcmp(this->fVAPtr, that.fVAPtr, this->fVACount * sizeof(GrVertexAttrib)) ||
343 this->fStencilSettings != that.fStencilSettings ||
344 this->fDrawFace != that.fDrawFace ||
345 this->fInputColorIsUsed != that.fInputColorIsUsed ||
346 this->fInputCoverageIsUsed != that.fInputCoverageIsUsed ||
347 this->fReadsDst != that.fReadsDst ||
348 this->fReadsFragPosition != that.fReadsFragPosition ||
349 this->fRequiresLocalCoordAttrib != that.fRequiresLocalCoordAttrib ||
350 this->fPrimaryOutputType != that.fPrimaryOutputType ||
351 this->fSecondaryOutputType != that.fSecondaryOutputType) {
352 return false;
353 }
354
355 bool usingVertexCoverage = this->hasCoverageVertexAttribute();
356 if (!usingVertexCoverage && this->fCoverage != that.fCoverage) {
357 return false;
358 }
359
360 bool explicitLocalCoords = this->hasLocalCoordAttribute();
361 if (this->hasGeometryProcessor()) {
362 if (!that.hasGeometryProcessor()) {
363 return false;
joshualitta5305a12014-10-10 17:47:00 -0700364 } else if (!this->getGeometryProcessor()->isEqual(*that.getGeometryProcessor())) {
egdaniel89af44a2014-09-26 06:15:04 -0700365 return false;
366 }
367 } else if (that.hasGeometryProcessor()) {
368 return false;
369 }
370
egdanield9aa2182014-10-09 13:47:05 -0700371 for (int i = 0; i < this->numFragmentStages(); i++) {
joshualitta5305a12014-10-10 17:47:00 -0700372 if (!GrFragmentStage::AreCompatible(this->getFragmentStage(i), that.getFragmentStage(i),
373 explicitLocalCoords)) {
egdaniel89af44a2014-09-26 06:15:04 -0700374 return false;
375 }
376 }
377
378 SkASSERT(0 == memcmp(this->fFixedFunctionVertexAttribIndices,
379 that.fFixedFunctionVertexAttribIndices,
380 sizeof(this->fFixedFunctionVertexAttribIndices)));
381
382 return true;
383}
384