blob: 371266b0830ec2c629a52aadc5e669bd21120a68 [file] [log] [blame]
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
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
17#include <GLES2/gl2.h>
18#include <GLES2/gl2ext.h>
19
20#include <rs_hal.h>
21#include <rsContext.h>
22#include <rsProgram.h>
23
Alex Sakhartchouk7f126c72011-05-05 16:56:27 -070024#include "rsdCore.h"
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -070025#include "rsdShader.h"
26#include "rsdShaderCache.h"
27
28using namespace android;
29using namespace android::renderscript;
30
31RsdShader::RsdShader(const Program *p, uint32_t type,
32 const char * shaderText, uint32_t shaderLength) {
33
34 mUserShader.setTo(shaderText, shaderLength);
35 mRSProgram = p;
36 mType = type;
37 initMemberVars();
38 initAttribAndUniformArray();
39 init();
40}
41
42RsdShader::~RsdShader() {
43 if (mShaderID) {
44 glDeleteShader(mShaderID);
45 }
46
47 delete[] mAttribNames;
48 delete[] mUniformNames;
49 delete[] mUniformArraySizes;
50}
51
52void RsdShader::initMemberVars() {
53 mDirty = true;
54 mShaderID = 0;
55 mAttribCount = 0;
56 mUniformCount = 0;
57
58 mAttribNames = NULL;
59 mUniformNames = NULL;
60 mUniformArraySizes = NULL;
61
62 mIsValid = false;
63}
64
65void RsdShader::init() {
66 uint32_t attribCount = 0;
67 uint32_t uniformCount = 0;
68 for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) {
69 initAddUserElement(mRSProgram->mHal.state.inputElements[ct].get(), mAttribNames, NULL, &attribCount, RS_SHADER_ATTR);
70 }
71 for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) {
72 initAddUserElement(mRSProgram->mHal.state.constantTypes[ct]->getElement(), mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI);
73 }
74
75 mTextureUniformIndexStart = uniformCount;
76 char buf[256];
77 for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) {
78 snprintf(buf, sizeof(buf), "UNI_Tex%i", ct);
79 mUniformNames[uniformCount].setTo(buf);
80 mUniformArraySizes[uniformCount] = 1;
81 uniformCount++;
82 }
83}
84
85String8 RsdShader::getGLSLInputString() const {
86 String8 s;
87 for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) {
88 const Element *e = mRSProgram->mHal.state.inputElements[ct].get();
89 for (uint32_t field=0; field < e->getFieldCount(); field++) {
90 const Element *f = e->getField(field);
91
92 // Cannot be complex
93 rsAssert(!f->getFieldCount());
94 switch (f->getComponent().getVectorSize()) {
95 case 1: s.append("attribute float ATTRIB_"); break;
96 case 2: s.append("attribute vec2 ATTRIB_"); break;
97 case 3: s.append("attribute vec3 ATTRIB_"); break;
98 case 4: s.append("attribute vec4 ATTRIB_"); break;
99 default:
100 rsAssert(0);
101 }
102
103 s.append(e->getFieldName(field));
104 s.append(";\n");
105 }
106 }
107 return s;
108}
109
110void RsdShader::appendAttributes() {
111 for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) {
112 const Element *e = mRSProgram->mHal.state.inputElements[ct].get();
113 for (uint32_t field=0; field < e->getFieldCount(); field++) {
114 const Element *f = e->getField(field);
115 const char *fn = e->getFieldName(field);
116
117 if (fn[0] == '#') {
118 continue;
119 }
120
121 // Cannot be complex
122 rsAssert(!f->getFieldCount());
123 switch (f->getComponent().getVectorSize()) {
124 case 1: mShader.append("attribute float ATTRIB_"); break;
125 case 2: mShader.append("attribute vec2 ATTRIB_"); break;
126 case 3: mShader.append("attribute vec3 ATTRIB_"); break;
127 case 4: mShader.append("attribute vec4 ATTRIB_"); break;
128 default:
129 rsAssert(0);
130 }
131
132 mShader.append(fn);
133 mShader.append(";\n");
134 }
135 }
136}
137
138void RsdShader::appendTextures() {
139 char buf[256];
140 for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) {
141 if (mRSProgram->mHal.state.textureTargets[ct] == RS_TEXTURE_2D) {
142 snprintf(buf, sizeof(buf), "uniform sampler2D UNI_Tex%i;\n", ct);
143 } else {
144 snprintf(buf, sizeof(buf), "uniform samplerCube UNI_Tex%i;\n", ct);
145 }
146 mShader.append(buf);
147 }
148}
149
150bool RsdShader::createShader() {
151
152 if (mType == GL_FRAGMENT_SHADER) {
153 mShader.append("precision mediump float;\n");
154 }
155 appendUserConstants();
156 appendAttributes();
157 appendTextures();
158
159 mShader.append(mUserShader);
160
161 return true;
162}
163
164bool RsdShader::loadShader(const Context *rsc) {
165 mShaderID = glCreateShader(mType);
166 rsAssert(mShaderID);
167
168 if (rsc->props.mLogShaders) {
169 LOGV("Loading shader type %x, ID %i", mType, mShaderID);
170 LOGV("%s", mShader.string());
171 }
172
173 if (mShaderID) {
174 const char * ss = mShader.string();
175 glShaderSource(mShaderID, 1, &ss, NULL);
176 glCompileShader(mShaderID);
177
178 GLint compiled = 0;
179 glGetShaderiv(mShaderID, GL_COMPILE_STATUS, &compiled);
180 if (!compiled) {
181 GLint infoLen = 0;
182 glGetShaderiv(mShaderID, GL_INFO_LOG_LENGTH, &infoLen);
183 if (infoLen) {
184 char* buf = (char*) malloc(infoLen);
185 if (buf) {
186 glGetShaderInfoLog(mShaderID, infoLen, NULL, buf);
187 LOGE("Could not compile shader \n%s\n", buf);
188 free(buf);
189 }
190 glDeleteShader(mShaderID);
191 mShaderID = 0;
192 rsc->setError(RS_ERROR_BAD_SHADER, "Error returned from GL driver loading shader text,");
193 return false;
194 }
195 }
196 }
197
198 if (rsc->props.mLogShaders) {
199 LOGV("--Shader load result %x ", glGetError());
200 }
201 mIsValid = true;
202 return true;
203}
204
205void RsdShader::appendUserConstants() {
206 for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) {
207 const Element *e = mRSProgram->mHal.state.constantTypes[ct]->getElement();
208 for (uint32_t field=0; field < e->getFieldCount(); field++) {
209 const Element *f = e->getField(field);
210 const char *fn = e->getFieldName(field);
211
212 if (fn[0] == '#') {
213 continue;
214 }
215
216 // Cannot be complex
217 rsAssert(!f->getFieldCount());
218 if (f->getType() == RS_TYPE_MATRIX_4X4) {
219 mShader.append("uniform mat4 UNI_");
220 } else if (f->getType() == RS_TYPE_MATRIX_3X3) {
221 mShader.append("uniform mat3 UNI_");
222 } else if (f->getType() == RS_TYPE_MATRIX_2X2) {
223 mShader.append("uniform mat2 UNI_");
224 } else {
225 switch (f->getComponent().getVectorSize()) {
226 case 1: mShader.append("uniform float UNI_"); break;
227 case 2: mShader.append("uniform vec2 UNI_"); break;
228 case 3: mShader.append("uniform vec3 UNI_"); break;
229 case 4: mShader.append("uniform vec4 UNI_"); break;
230 default:
231 rsAssert(0);
232 }
233 }
234
235 mShader.append(fn);
236 if (e->getFieldArraySize(field) > 1) {
237 mShader.appendFormat("[%d]", e->getFieldArraySize(field));
238 }
239 mShader.append(";\n");
240 }
241 }
242}
243
244void RsdShader::logUniform(const Element *field, const float *fd, uint32_t arraySize ) {
245 RsDataType dataType = field->getType();
246 uint32_t elementSize = field->getSizeBytes() / sizeof(float);
247 for (uint32_t i = 0; i < arraySize; i ++) {
248 if (arraySize > 1) {
249 LOGV("Array Element [%u]", i);
250 }
251 if (dataType == RS_TYPE_MATRIX_4X4) {
252 LOGV("Matrix4x4");
253 LOGV("{%f, %f, %f, %f", fd[0], fd[4], fd[8], fd[12]);
254 LOGV(" %f, %f, %f, %f", fd[1], fd[5], fd[9], fd[13]);
255 LOGV(" %f, %f, %f, %f", fd[2], fd[6], fd[10], fd[14]);
256 LOGV(" %f, %f, %f, %f}", fd[3], fd[7], fd[11], fd[15]);
257 } else if (dataType == RS_TYPE_MATRIX_3X3) {
258 LOGV("Matrix3x3");
259 LOGV("{%f, %f, %f", fd[0], fd[3], fd[6]);
260 LOGV(" %f, %f, %f", fd[1], fd[4], fd[7]);
261 LOGV(" %f, %f, %f}", fd[2], fd[5], fd[8]);
262 } else if (dataType == RS_TYPE_MATRIX_2X2) {
263 LOGV("Matrix2x2");
264 LOGV("{%f, %f", fd[0], fd[2]);
265 LOGV(" %f, %f}", fd[1], fd[3]);
266 } else {
267 switch (field->getComponent().getVectorSize()) {
268 case 1:
269 LOGV("Uniform 1 = %f", fd[0]);
270 break;
271 case 2:
272 LOGV("Uniform 2 = %f %f", fd[0], fd[1]);
273 break;
274 case 3:
275 LOGV("Uniform 3 = %f %f %f", fd[0], fd[1], fd[2]);
276 break;
277 case 4:
278 LOGV("Uniform 4 = %f %f %f %f", fd[0], fd[1], fd[2], fd[3]);
279 break;
280 default:
281 rsAssert(0);
282 }
283 }
284 LOGE("Element size %u data=%p", elementSize, fd);
285 fd += elementSize;
286 LOGE("New data=%p", fd);
287 }
288}
289
290void RsdShader::setUniform(const Context *rsc, const Element *field, const float *fd,
291 int32_t slot, uint32_t arraySize ) {
292 RsDataType dataType = field->getType();
293 if (dataType == RS_TYPE_MATRIX_4X4) {
294 glUniformMatrix4fv(slot, arraySize, GL_FALSE, fd);
295 } else if (dataType == RS_TYPE_MATRIX_3X3) {
296 glUniformMatrix3fv(slot, arraySize, GL_FALSE, fd);
297 } else if (dataType == RS_TYPE_MATRIX_2X2) {
298 glUniformMatrix2fv(slot, arraySize, GL_FALSE, fd);
299 } else {
300 switch (field->getComponent().getVectorSize()) {
301 case 1:
302 glUniform1fv(slot, arraySize, fd);
303 break;
304 case 2:
305 glUniform2fv(slot, arraySize, fd);
306 break;
307 case 3:
308 glUniform3fv(slot, arraySize, fd);
309 break;
310 case 4:
311 glUniform4fv(slot, arraySize, fd);
312 break;
313 default:
314 rsAssert(0);
315 }
316 }
317}
318
Alex Sakhartchouk7f126c72011-05-05 16:56:27 -0700319void RsdShader::setupSampler(const Context *rsc, const Sampler *s, const Allocation *tex) {
320 RsdHal *dc = (RsdHal *)rsc->mHal.drv;
321
322 GLenum trans[] = {
323 GL_NEAREST, //RS_SAMPLER_NEAREST,
324 GL_LINEAR, //RS_SAMPLER_LINEAR,
325 GL_LINEAR_MIPMAP_LINEAR, //RS_SAMPLER_LINEAR_MIP_LINEAR,
326 GL_REPEAT, //RS_SAMPLER_WRAP,
327 GL_CLAMP_TO_EDGE, //RS_SAMPLER_CLAMP
328 GL_LINEAR_MIPMAP_NEAREST, //RS_SAMPLER_LINEAR_MIP_NEAREST
329 };
330
331 GLenum transNP[] = {
332 GL_NEAREST, //RS_SAMPLER_NEAREST,
333 GL_LINEAR, //RS_SAMPLER_LINEAR,
334 GL_LINEAR, //RS_SAMPLER_LINEAR_MIP_LINEAR,
335 GL_CLAMP_TO_EDGE, //RS_SAMPLER_WRAP,
336 GL_CLAMP_TO_EDGE, //RS_SAMPLER_CLAMP
337 GL_LINEAR, //RS_SAMPLER_LINEAR_MIP_NEAREST,
338 };
339
340 // This tells us the correct texture type
341 GLenum target = (GLenum)tex->getGLTarget();
342
343 if (!dc->gl.gl.OES_texture_npot && tex->getType()->getIsNp2()) {
344 if (tex->getHasGraphicsMipmaps() &&
345 (dc->gl.gl.GL_NV_texture_npot_2D_mipmap || dc->gl.gl.GL_IMG_texture_npot)) {
346 if (dc->gl.gl.GL_NV_texture_npot_2D_mipmap) {
347 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, trans[s->mHal.state.minFilter]);
348 } else {
349 switch (trans[s->mHal.state.minFilter]) {
350 case GL_LINEAR_MIPMAP_LINEAR:
351 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
352 break;
353 default:
354 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, trans[s->mHal.state.minFilter]);
355 break;
356 }
357 }
358 } else {
359 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, transNP[s->mHal.state.minFilter]);
360 }
361 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, transNP[s->mHal.state.magFilter]);
362 glTexParameteri(target, GL_TEXTURE_WRAP_S, transNP[s->mHal.state.wrapS]);
363 glTexParameteri(target, GL_TEXTURE_WRAP_T, transNP[s->mHal.state.wrapT]);
364 } else {
365 if (tex->getHasGraphicsMipmaps()) {
366 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, trans[s->mHal.state.minFilter]);
367 } else {
368 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, transNP[s->mHal.state.minFilter]);
369 }
370 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, trans[s->mHal.state.magFilter]);
371 glTexParameteri(target, GL_TEXTURE_WRAP_S, trans[s->mHal.state.wrapS]);
372 glTexParameteri(target, GL_TEXTURE_WRAP_T, trans[s->mHal.state.wrapT]);
373 }
374
375 float anisoValue = rsMin(dc->gl.gl.EXT_texture_max_aniso, s->mHal.state.aniso);
376 if (dc->gl.gl.EXT_texture_max_aniso > 1.0f) {
377 glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoValue);
378 }
379
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700380 rsdGLCheckError(rsc, "Sampler::setup tex env");
Alex Sakhartchouk7f126c72011-05-05 16:56:27 -0700381}
382
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700383void RsdShader::setupTextures(const Context *rsc, RsdShaderCache *sc) {
384 if (mRSProgram->mHal.state.texturesCount == 0) {
385 return;
386 }
387
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700388 RsdHal *dc = (RsdHal *)rsc->mHal.drv;
389
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700390 uint32_t numTexturesToBind = mRSProgram->mHal.state.texturesCount;
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700391 uint32_t numTexturesAvailable = dc->gl.gl.maxFragmentTextureImageUnits;
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700392 if (numTexturesToBind >= numTexturesAvailable) {
393 LOGE("Attempting to bind %u textures on shader id %u, but only %u are available",
394 mRSProgram->mHal.state.texturesCount, (uint32_t)this, numTexturesAvailable);
395 rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind more textuers than available");
396 numTexturesToBind = numTexturesAvailable;
397 }
398
399 for (uint32_t ct=0; ct < numTexturesToBind; ct++) {
400 glActiveTexture(GL_TEXTURE0 + ct);
401 if (!mRSProgram->mHal.state.textures[ct].get()) {
402 LOGE("No texture bound for shader id %u, texture unit %u", (uint)this, ct);
403 rsc->setError(RS_ERROR_BAD_SHADER, "No texture bound");
404 continue;
405 }
406
407 GLenum target = (GLenum)mRSProgram->mHal.state.textures[ct]->getGLTarget();
408 if (target != GL_TEXTURE_2D && target != GL_TEXTURE_CUBE_MAP) {
409 LOGE("Attempting to bind unknown texture to shader id %u, texture unit %u", (uint)this, ct);
410 rsc->setError(RS_ERROR_BAD_SHADER, "Non-texture allocation bound to a shader");
411 }
412 glBindTexture(target, mRSProgram->mHal.state.textures[ct]->getTextureID());
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700413 rsdGLCheckError(rsc, "ProgramFragment::setup tex bind");
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700414 if (mRSProgram->mHal.state.samplers[ct].get()) {
Alex Sakhartchouk7f126c72011-05-05 16:56:27 -0700415 setupSampler(rsc, mRSProgram->mHal.state.samplers[ct].get(), mRSProgram->mHal.state.textures[ct].get());
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700416 } else {
417 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
418 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
419 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
420 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700421 rsdGLCheckError(rsc, "ProgramFragment::setup tex env");
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700422 }
423
424 glUniform1i(sc->fragUniformSlot(mTextureUniformIndexStart + ct), ct);
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700425 rsdGLCheckError(rsc, "ProgramFragment::setup uniforms");
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700426 }
427
428 glActiveTexture(GL_TEXTURE0);
429 mDirty = false;
Alex Sakhartchoukc19ff012011-05-06 14:59:45 -0700430 rsdGLCheckError(rsc, "ProgramFragment::setup");
Alex Sakhartchouka04e30d2011-04-29 16:49:08 -0700431}
432
433void RsdShader::setupUserConstants(const Context *rsc, RsdShaderCache *sc, bool isFragment) {
434 uint32_t uidx = 0;
435 for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) {
436 Allocation *alloc = mRSProgram->mHal.state.constants[ct].get();
437 if (!alloc) {
438 LOGE("Attempting to set constants on shader id %u, but alloc at slot %u is not set", (uint32_t)this, ct);
439 rsc->setError(RS_ERROR_BAD_SHADER, "No constant allocation bound");
440 continue;
441 }
442
443 const uint8_t *data = static_cast<const uint8_t *>(alloc->getPtr());
444 const Element *e = mRSProgram->mHal.state.constantTypes[ct]->getElement();
445 for (uint32_t field=0; field < e->getFieldCount(); field++) {
446 const Element *f = e->getField(field);
447 const char *fieldName = e->getFieldName(field);
448 // If this field is padding, skip it
449 if (fieldName[0] == '#') {
450 continue;
451 }
452
453 uint32_t offset = e->getFieldOffsetBytes(field);
454 const float *fd = reinterpret_cast<const float *>(&data[offset]);
455
456 int32_t slot = -1;
457 uint32_t arraySize = 1;
458 if (!isFragment) {
459 slot = sc->vtxUniformSlot(uidx);
460 arraySize = sc->vtxUniformSize(uidx);
461 } else {
462 slot = sc->fragUniformSlot(uidx);
463 arraySize = sc->fragUniformSize(uidx);
464 }
465 if (rsc->props.mLogShadersUniforms) {
466 LOGV("Uniform slot=%i, offset=%i, constant=%i, field=%i, uidx=%i, name=%s", slot, offset, ct, field, uidx, fieldName);
467 }
468 uidx ++;
469 if (slot < 0) {
470 continue;
471 }
472
473 if (rsc->props.mLogShadersUniforms) {
474 logUniform(f, fd, arraySize);
475 }
476 setUniform(rsc, f, fd, slot, arraySize);
477 }
478 }
479}
480
481void RsdShader::setup(const android::renderscript::Context *rsc, RsdShaderCache *sc) {
482
483 setupUserConstants(rsc, sc, mType == GL_FRAGMENT_SHADER);
484 setupTextures(rsc, sc);
485}
486
487void RsdShader::initAttribAndUniformArray() {
488 mAttribCount = 0;
489 for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) {
490 const Element *elem = mRSProgram->mHal.state.inputElements[ct].get();
491 for (uint32_t field=0; field < elem->getFieldCount(); field++) {
492 if (elem->getFieldName(field)[0] != '#') {
493 mAttribCount ++;
494 }
495 }
496 }
497
498 mUniformCount = 0;
499 for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) {
500 const Element *elem = mRSProgram->mHal.state.constantTypes[ct]->getElement();
501
502 for (uint32_t field=0; field < elem->getFieldCount(); field++) {
503 if (elem->getFieldName(field)[0] != '#') {
504 mUniformCount ++;
505 }
506 }
507 }
508 mUniformCount += mRSProgram->mHal.state.texturesCount;
509
510 if (mAttribCount) {
511 mAttribNames = new String8[mAttribCount];
512 }
513 if (mUniformCount) {
514 mUniformNames = new String8[mUniformCount];
515 mUniformArraySizes = new uint32_t[mUniformCount];
516 }
517}
518
519void RsdShader::initAddUserElement(const Element *e, String8 *names, uint32_t *arrayLengths, uint32_t *count, const char *prefix) {
520 rsAssert(e->getFieldCount());
521 for (uint32_t ct=0; ct < e->getFieldCount(); ct++) {
522 const Element *ce = e->getField(ct);
523 if (ce->getFieldCount()) {
524 initAddUserElement(ce, names, arrayLengths, count, prefix);
525 } else if (e->getFieldName(ct)[0] != '#') {
526 String8 tmp(prefix);
527 tmp.append(e->getFieldName(ct));
528 names[*count].setTo(tmp.string());
529 if (arrayLengths) {
530 arrayLengths[*count] = e->getFieldArraySize(ct);
531 }
532 (*count)++;
533 }
534 }
535}