blob: 1798e604db121bda747bace1d4923b89beb1e28c [file] [log] [blame]
joshualitt30ba4362014-08-21 20:18:45 -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#ifndef GrGLProgramBuilder_DEFINED
9#define GrGLProgramBuilder_DEFINED
10
joshualitt30ba4362014-08-21 20:18:45 -070011#include "GrGLFragmentShaderBuilder.h"
12#include "GrGLGeometryShaderBuilder.h"
13#include "GrGLVertexShaderBuilder.h"
joshualitt47bb3822014-10-07 16:43:25 -070014#include "../GrGLProgramDataManager.h"
15#include "../GrGLUniformHandle.h"
joshualitta5305a12014-10-10 17:47:00 -070016#include "../GrGLGeometryProcessor.h"
bsalomon04ddf892014-11-19 12:36:22 -080017#include "../../GrOptDrawState.h"
bsalomonae59b772014-11-19 08:23:49 -080018#include "../../GrPendingFragmentStage.h"
joshualitt89c7a2e2014-10-10 14:11:59 -070019
joshualitt47bb3822014-10-07 16:43:25 -070020/*
21 * This is the base class for a series of interfaces. This base class *MUST* remain abstract with
22 * NO data members because it is used in multiple interface inheritance.
23 * Heirarchy:
24 * GrGLUniformBuilder
25 * / \
26 * GrGLFPBuilder GrGLGPBuilder
27 * \ /
28 * GrGLProgramBuilder(internal use only)
29 */
30class GrGLUniformBuilder {
joshualitt30ba4362014-08-21 20:18:45 -070031public:
32 enum ShaderVisibility {
bsalomon17168df2014-12-09 09:00:49 -080033 kVertex_Visibility = 1 << kVertex_GrShaderType,
34 kGeometry_Visibility = 1 << kGeometry_GrShaderType,
35 kFragment_Visibility = 1 << kFragment_GrShaderType,
joshualitt30ba4362014-08-21 20:18:45 -070036 };
37
joshualitt47bb3822014-10-07 16:43:25 -070038 virtual ~GrGLUniformBuilder() {}
39
joshualitt30ba4362014-08-21 20:18:45 -070040 typedef GrGLProgramDataManager::UniformHandle UniformHandle;
joshualitt47bb3822014-10-07 16:43:25 -070041
42 /** Add a uniform variable to the current program, that has visibility in one or more shaders.
43 visibility is a bitfield of ShaderVisibility values indicating from which shaders the
44 uniform should be accessible. At least one bit must be set. Geometry shader uniforms are not
45 supported at this time. The actual uniform name will be mangled. If outName is not NULL then
46 it will refer to the final uniform name after return. Use the addUniformArray variant to add
47 an array of uniforms. */
bsalomon422f56f2014-12-09 10:18:12 -080048 UniformHandle addUniform(uint32_t visibility,
49 GrSLType type,
50 GrSLPrecision precision,
51 const char* name,
52 const char** outName = NULL) {
53 return this->addUniformArray(visibility, type, precision, name, 0, outName);
54 }
55
56 virtual UniformHandle addUniformArray(
57 uint32_t visibility,
58 GrSLType type,
59 GrSLPrecision precision,
60 const char* name,
61 int arrayCount,
62 const char** outName = NULL) = 0;
joshualitt47bb3822014-10-07 16:43:25 -070063
64 virtual const GrGLShaderVar& getUniformVariable(UniformHandle u) const = 0;
65
66 /**
67 * Shortcut for getUniformVariable(u).c_str()
68 */
69 virtual const char* getUniformCStr(UniformHandle u) const = 0;
70
71 virtual const GrGLContextInfo& ctxInfo() const = 0;
72
73 virtual GrGpuGL* gpu() const = 0;
74
75 /*
76 * *NOTE* NO MEMBERS ALLOWED, MULTIPLE INHERITANCE
77 */
78};
79
joshualitt74077b92014-10-24 11:26:03 -070080// TODO move this into GrGLGPBuilder and move them both out of this file
81class GrGLVarying {
82public:
83 bool vsVarying() const { return kVertToFrag_Varying == fVarying ||
84 kVertToGeo_Varying == fVarying; }
85 bool fsVarying() const { return kVertToFrag_Varying == fVarying ||
86 kGeoToFrag_Varying == fVarying; }
87 const char* vsOut() const { return fVsOut; }
88 const char* gsIn() const { return fGsIn; }
89 const char* gsOut() const { return fGsOut; }
90 const char* fsIn() const { return fFsIn; }
91
92protected:
93 enum Varying {
94 kVertToFrag_Varying,
95 kVertToGeo_Varying,
96 kGeoToFrag_Varying,
97 };
98
99 GrGLVarying(GrSLType type, Varying varying)
100 : fVarying(varying), fType(type), fVsOut(NULL), fGsIn(NULL), fGsOut(NULL),
101 fFsIn(NULL) {}
102
103 Varying fVarying;
104
105private:
106 GrSLType fType;
107 const char* fVsOut;
108 const char* fGsIn;
109 const char* fGsOut;
110 const char* fFsIn;
111
112 friend class GrGLVertexBuilder;
113 friend class GrGLGeometryBuilder;
114 friend class GrGLFragmentShaderBuilder;
115};
116
117struct GrGLVertToFrag : public GrGLVarying {
118 GrGLVertToFrag(GrSLType type)
119 : GrGLVarying(type, kVertToFrag_Varying) {}
120};
121
122struct GrGLVertToGeo : public GrGLVarying {
123 GrGLVertToGeo(GrSLType type)
124 : GrGLVarying(type, kVertToGeo_Varying) {}
125};
126
127struct GrGLGeoToFrag : public GrGLVarying {
128 GrGLGeoToFrag(GrSLType type)
129 : GrGLVarying(type, kGeoToFrag_Varying) {}
130};
131
joshualitt47bb3822014-10-07 16:43:25 -0700132/* a specialization of the above for GPs. Lets the user add uniforms, varyings, and VS / FS code */
133class GrGLGPBuilder : public virtual GrGLUniformBuilder {
134public:
joshualitt2dd1ae02014-12-03 06:24:10 -0800135 /*
136 * addVarying allows fine grained control for setting up varyings between stages. If you just
137 * need to take an attribute and pass it through to an output value in a fragment shader, use
138 * addPassThroughAttribute.
139 * TODO convert most uses of addVarying to addPassThroughAttribute
140 */
joshualitt74077b92014-10-24 11:26:03 -0700141 virtual void addVarying(const char* name,
142 GrGLVarying*,
bsalomonc0bd6482014-12-09 10:04:14 -0800143 GrSLPrecision fsPrecision = kDefault_GrSLPrecision) = 0;
joshualitt47bb3822014-10-07 16:43:25 -0700144
joshualitt2dd1ae02014-12-03 06:24:10 -0800145 /*
146 * This call can be used by GP to pass an attribute through all shaders directly to 'output' in
147 * the fragment shader. Though this call effects both the vertex shader and fragment shader,
148 * it expects 'output' to be defined in the fragment shader before this call is made.
149 * TODO it might be nicer behavior to have a flag to declare output inside this call
150 */
151 virtual void addPassThroughAttribute(const GrGeometryProcessor::GrAttribute*,
152 const char* output) = 0;
153
joshualitt47bb3822014-10-07 16:43:25 -0700154 // TODO rename getFragmentBuilder
155 virtual GrGLGPFragmentBuilder* getFragmentShaderBuilder() = 0;
156 virtual GrGLVertexBuilder* getVertexShaderBuilder() = 0;
157
158 /*
159 * *NOTE* NO MEMBERS ALLOWED, MULTIPLE INHERITANCE
160 */
161};
162
163/* a specializations for FPs. Lets the user add uniforms and FS code */
164class GrGLFPBuilder : public virtual GrGLUniformBuilder {
165public:
166 virtual GrGLFPFragmentBuilder* getFragmentShaderBuilder() = 0;
167
168 /*
169 * *NOTE* NO MEMBERS ALLOWED, MULTIPLE INHERITANCE
170 */
171};
172
joshualitta5305a12014-10-10 17:47:00 -0700173struct GrGLInstalledProc;
174struct GrGLInstalledGeoProc;
175struct GrGLInstalledFragProc;
176struct GrGLInstalledFragProcs;
177
joshualitt47bb3822014-10-07 16:43:25 -0700178/*
179 * Please note - no diamond problems because of virtual inheritance. Also, both base classes
180 * are pure virtual with no data members. This is the base class for program building.
181 * Subclasses are nearly identical but each has their own way of emitting transforms. State for
182 * each of the elements of the shader pipeline, ie vertex, fragment, geometry, etc, lives in those
183 * respective builders
184*/
185class GrGLProgramBuilder : public GrGLGPBuilder,
joshualitt29473822014-12-10 15:03:00 -0800186 public GrGLFPBuilder {
joshualitt47bb3822014-10-07 16:43:25 -0700187public:
188 /** Generates a shader program.
189 *
190 * The program implements what is specified in the stages given as input.
191 * After successful generation, the builder result objects are available
192 * to be used.
193 * @return true if generation was successful.
194 */
joshualittdafa4d02014-12-04 08:59:10 -0800195 static GrGLProgram* CreateProgram(const GrOptDrawState&, GrGpuGL*);
joshualitt47bb3822014-10-07 16:43:25 -0700196
bsalomon422f56f2014-12-09 10:18:12 -0800197 UniformHandle addUniformArray(uint32_t visibility,
198 GrSLType type,
199 GrSLPrecision precision,
200 const char* name,
201 int arrayCount,
202 const char** outName) SK_OVERRIDE;
joshualitt47bb3822014-10-07 16:43:25 -0700203
bsalomon422f56f2014-12-09 10:18:12 -0800204 const GrGLShaderVar& getUniformVariable(UniformHandle u) const SK_OVERRIDE {
joshualitt47bb3822014-10-07 16:43:25 -0700205 return fUniforms[u.toShaderBuilderIndex()].fVariable;
206 }
207
bsalomon422f56f2014-12-09 10:18:12 -0800208 const char* getUniformCStr(UniformHandle u) const SK_OVERRIDE {
joshualitt47bb3822014-10-07 16:43:25 -0700209 return this->getUniformVariable(u).c_str();
210 }
211
bsalomon422f56f2014-12-09 10:18:12 -0800212 const GrGLContextInfo& ctxInfo() const SK_OVERRIDE;
joshualitt47bb3822014-10-07 16:43:25 -0700213
bsalomon422f56f2014-12-09 10:18:12 -0800214 GrGpuGL* gpu() const SK_OVERRIDE { return fGpu; }
joshualitt47bb3822014-10-07 16:43:25 -0700215
bsalomon422f56f2014-12-09 10:18:12 -0800216 GrGLFPFragmentBuilder* getFragmentShaderBuilder() SK_OVERRIDE { return &fFS; }
217 GrGLVertexBuilder* getVertexShaderBuilder() SK_OVERRIDE { return &fVS; }
joshualitt47bb3822014-10-07 16:43:25 -0700218
bsalomon422f56f2014-12-09 10:18:12 -0800219 void addVarying(
joshualitta5305a12014-10-10 17:47:00 -0700220 const char* name,
joshualitt74077b92014-10-24 11:26:03 -0700221 GrGLVarying*,
bsalomonc0bd6482014-12-09 10:04:14 -0800222 GrSLPrecision fsPrecision = kDefault_GrSLPrecision) SK_OVERRIDE;
joshualitt30ba4362014-08-21 20:18:45 -0700223
bsalomon422f56f2014-12-09 10:18:12 -0800224 void addPassThroughAttribute(const GrGeometryProcessor::GrAttribute*,
joshualitt2dd1ae02014-12-03 06:24:10 -0800225 const char* output) SK_OVERRIDE;
226
227
joshualitt30ba4362014-08-21 20:18:45 -0700228 // Handles for program uniforms (other than per-effect uniforms)
229 struct BuiltinUniformHandles {
230 UniformHandle fViewMatrixUni;
231 UniformHandle fRTAdjustmentUni;
232 UniformHandle fColorUni;
233 UniformHandle fCoverageUni;
234
235 // We use the render target height to provide a y-down frag coord when specifying
236 // origin_upper_left is not supported.
237 UniformHandle fRTHeightUni;
238
239 // Uniforms for computing texture coords to do the dst-copy lookup
240 UniformHandle fDstCopyTopLeftUni;
241 UniformHandle fDstCopyScaleUni;
242 UniformHandle fDstCopySamplerUni;
243 };
244
joshualittdb0d3ca2014-10-07 12:42:26 -0700245protected:
joshualitta5305a12014-10-10 17:47:00 -0700246 typedef GrGLProgramDataManager::UniformInfo UniformInfo;
247 typedef GrGLProgramDataManager::UniformInfoArray UniformInfoArray;
248
joshualitt79f8fae2014-10-28 17:59:26 -0700249 static GrGLProgramBuilder* CreateProgramBuilder(const GrOptDrawState&,
joshualitt47bb3822014-10-07 16:43:25 -0700250 bool hasGeometryProcessor,
251 GrGpuGL*);
252
joshualitt79f8fae2014-10-28 17:59:26 -0700253 GrGLProgramBuilder(GrGpuGL*, const GrOptDrawState&);
joshualitt30ba4362014-08-21 20:18:45 -0700254
egdaniel307796b2014-10-06 12:13:54 -0700255 const GrOptDrawState& optState() const { return fOptState; }
joshualitt79f8fae2014-10-28 17:59:26 -0700256 const GrProgramDesc& desc() const { return fDesc; }
257 const GrProgramDesc::KeyHeader& header() const { return fDesc.header(); }
joshualitt23e280d2014-09-18 12:26:38 -0700258
joshualitt30ba4362014-08-21 20:18:45 -0700259 // Generates a name for a variable. The generated string will be name prefixed by the prefix
260 // char (unless the prefix is '\0'). It also mangles the name to be stage-specific if we're
261 // generating stage code.
262 void nameVariable(SkString* out, char prefix, const char* name);
egdaniel37b4d862014-11-03 10:07:07 -0800263 void setupUniformColorAndCoverageIfNeeded(GrGLSLExpr4* inputColor, GrGLSLExpr1* inputCoverage);
joshualitt2dd1ae02014-12-03 06:24:10 -0800264 // Generates a possibly mangled name for a stage variable and writes it to the fragment shader.
265 // If GrGLSLExpr4 has a valid name then it will use that instead
266 void nameExpression(GrGLSLExpr4*, const char* baseName);
joshualitt4973d9d2014-11-08 09:24:25 -0800267 void emitAndInstallProcs(GrGLSLExpr4* inputColor,
joshualitta5305a12014-10-10 17:47:00 -0700268 GrGLSLExpr4* inputCoverage);
269 void emitAndInstallFragProcs(int procOffset, int numProcs, GrGLSLExpr4* inOut);
joshualitt2dd1ae02014-12-03 06:24:10 -0800270 void emitAndInstallProc(const GrPendingFragmentStage&,
joshualitta5305a12014-10-10 17:47:00 -0700271 int index,
joshualitta5305a12014-10-10 17:47:00 -0700272 const GrGLSLExpr4& input,
273 GrGLSLExpr4* output);
274
joshualitt2dd1ae02014-12-03 06:24:10 -0800275 void emitAndInstallProc(const GrGeometryProcessor&,
276 GrGLSLExpr4* outputColor,
277 GrGLSLExpr4* outputCoverage);
278
joshualitta5305a12014-10-10 17:47:00 -0700279 // these emit functions help to keep the createAndEmitProcessors template general
bsalomonae59b772014-11-19 08:23:49 -0800280 void emitAndInstallProc(const GrPendingFragmentStage&,
joshualitta5305a12014-10-10 17:47:00 -0700281 const char* outColor,
282 const char* inColor);
283 void emitAndInstallProc(const GrGeometryProcessor&,
joshualitt2dd1ae02014-12-03 06:24:10 -0800284 const char* outColor,
285 const char* outCoverage);
286
joshualitt47bb3822014-10-07 16:43:25 -0700287 void verify(const GrGeometryProcessor&);
288 void verify(const GrFragmentProcessor&);
289 void emitSamplers(const GrProcessor&,
290 GrGLProcessor::TextureSamplerArray* outSamplers,
joshualitta5305a12014-10-10 17:47:00 -0700291 GrGLInstalledProc*);
joshualitt30ba4362014-08-21 20:18:45 -0700292
joshualitt47bb3822014-10-07 16:43:25 -0700293 // each specific program builder has a distinct transform and must override this function
bsalomonae59b772014-11-19 08:23:49 -0800294 virtual void emitTransforms(const GrPendingFragmentStage&,
joshualitt47bb3822014-10-07 16:43:25 -0700295 GrGLProcessor::TransformedCoordsArray* outCoords,
joshualitta5305a12014-10-10 17:47:00 -0700296 GrGLInstalledFragProc*);
joshualitt47bb3822014-10-07 16:43:25 -0700297 GrGLProgram* finalize();
298 void bindUniformLocations(GrGLuint programID);
299 bool checkLinkStatus(GrGLuint programID);
300 void resolveUniformLocations(GrGLuint programID);
joshualitt47bb3822014-10-07 16:43:25 -0700301 void cleanupProgram(GrGLuint programID, const SkTDArray<GrGLuint>& shaderIDs);
302 void cleanupShaders(const SkTDArray<GrGLuint>& shaderIDs);
joshualitt30ba4362014-08-21 20:18:45 -0700303
joshualitt47bb3822014-10-07 16:43:25 -0700304 // Subclasses create different programs
305 virtual GrGLProgram* createProgram(GrGLuint programID);
306
joshualitt30ba4362014-08-21 20:18:45 -0700307 void appendUniformDecls(ShaderVisibility, SkString*) const;
308
joshualitt47bb3822014-10-07 16:43:25 -0700309 // reset is called by program creator between each processor's emit code. It increments the
310 // stage offset for variable name mangling, and also ensures verfication variables in the
311 // fragment shader are cleared.
312 void reset() {
313 this->enterStage();
314 this->addStage();
315 fFS.reset();
316 }
317 void addStage() { fStageIndex++; }
318
319 // This simple class exits the stage and then restores the stage when it goes out of scope
320 class AutoStageRestore {
joshualitt30ba4362014-08-21 20:18:45 -0700321 public:
joshualitt47bb3822014-10-07 16:43:25 -0700322 AutoStageRestore(GrGLProgramBuilder* pb)
323 : fPB(pb), fOutOfStage(pb->fOutOfStage) { pb->exitStage(); }
324 ~AutoStageRestore() { fPB->fOutOfStage = fOutOfStage; }
joshualittb0a8a372014-09-23 09:50:21 -0700325 private:
joshualitt47bb3822014-10-07 16:43:25 -0700326 GrGLProgramBuilder* fPB;
327 bool fOutOfStage;
joshualittb0a8a372014-09-23 09:50:21 -0700328 };
joshualitt47bb3822014-10-07 16:43:25 -0700329 class AutoStageAdvance {
joshualittdb0d3ca2014-10-07 12:42:26 -0700330 public:
joshualitt47bb3822014-10-07 16:43:25 -0700331 AutoStageAdvance(GrGLProgramBuilder* pb) : fPB(pb) { fPB->reset(); }
332 ~AutoStageAdvance() { fPB->exitStage(); }
joshualittdb0d3ca2014-10-07 12:42:26 -0700333 private:
joshualitt47bb3822014-10-07 16:43:25 -0700334 GrGLProgramBuilder* fPB;
335 };
336 void exitStage() { fOutOfStage = true; }
337 void enterStage() { fOutOfStage = false; }
338 int stageIndex() const { return fStageIndex; }
339
joshualitt4973d9d2014-11-08 09:24:25 -0800340 struct TransformVarying {
341 TransformVarying(const GrGLVarying& v, const char* uniName, const char* sourceCoords)
342 : fV(v), fUniName(uniName), fSourceCoords(sourceCoords) {}
343 GrGLVarying fV;
344 SkString fUniName;
345 SkString fSourceCoords;
346 };
347
joshualitt4973d9d2014-11-08 09:24:25 -0800348 const char* rtAdjustment() const { return "rtAdjustment"; }
349
joshualitt47bb3822014-10-07 16:43:25 -0700350 // number of each input/output type in a single allocation block, used by many builders
351 static const int kVarsPerBlock;
352
353 BuiltinUniformHandles fUniformHandles;
354 GrGLVertexBuilder fVS;
355 GrGLGeometryBuilder fGS;
356 GrGLFragmentShaderBuilder fFS;
357 bool fOutOfStage;
358 int fStageIndex;
359
joshualitta5305a12014-10-10 17:47:00 -0700360 GrGLInstalledGeoProc* fGeometryProcessor;
361 SkAutoTUnref<GrGLInstalledFragProcs> fFragmentProcessors;
joshualitt47bb3822014-10-07 16:43:25 -0700362
363 const GrOptDrawState& fOptState;
joshualitt79f8fae2014-10-28 17:59:26 -0700364 const GrProgramDesc& fDesc;
joshualitt47bb3822014-10-07 16:43:25 -0700365 GrGpuGL* fGpu;
366 UniformInfoArray fUniforms;
joshualitt4973d9d2014-11-08 09:24:25 -0800367 SkSTArray<16, TransformVarying, true> fCoordVaryings;
joshualitt47bb3822014-10-07 16:43:25 -0700368
369 friend class GrGLShaderBuilder;
370 friend class GrGLVertexBuilder;
371 friend class GrGLFragmentShaderBuilder;
372 friend class GrGLGeometryBuilder;
373};
374
375/**
joshualitta5305a12014-10-10 17:47:00 -0700376 * The below structs represent processors installed in programs. All processors can have texture
377 * samplers, but only frag processors have coord transforms, hence the need for different structs
joshualitt47bb3822014-10-07 16:43:25 -0700378 */
joshualitta5305a12014-10-10 17:47:00 -0700379struct GrGLInstalledProc {
380 typedef GrGLProgramDataManager::UniformHandle UniformHandle;
joshualitt47bb3822014-10-07 16:43:25 -0700381
joshualitta5305a12014-10-10 17:47:00 -0700382 struct Sampler {
383 SkDEBUGCODE(Sampler() : fTextureUnit(-1) {})
384 UniformHandle fUniform;
385 int fTextureUnit;
386 };
387 SkSTArray<4, Sampler, true> fSamplers;
388};
joshualitt47bb3822014-10-07 16:43:25 -0700389
joshualitta5305a12014-10-10 17:47:00 -0700390struct GrGLInstalledGeoProc : public GrGLInstalledProc {
391 SkAutoTDelete<GrGLGeometryProcessor> fGLProc;
392};
joshualitt47bb3822014-10-07 16:43:25 -0700393
joshualitta5305a12014-10-10 17:47:00 -0700394struct GrGLInstalledFragProc : public GrGLInstalledProc {
joshualitt2dd1ae02014-12-03 06:24:10 -0800395 GrGLInstalledFragProc() : fGLProc(NULL) {}
joshualitt47bb3822014-10-07 16:43:25 -0700396 class ShaderVarHandle {
397 public:
398 bool isValid() const { return fHandle > -1; }
399 ShaderVarHandle() : fHandle(-1) {}
400 ShaderVarHandle(int value) : fHandle(value) { SkASSERT(this->isValid()); }
401 int handle() const { SkASSERT(this->isValid()); return fHandle; }
402 UniformHandle convertToUniformHandle() {
403 SkASSERT(this->isValid());
404 return GrGLProgramDataManager::UniformHandle::CreateFromUniformIndex(fHandle);
405 }
joshualittdb0d3ca2014-10-07 12:42:26 -0700406
joshualitt47bb3822014-10-07 16:43:25 -0700407 private:
408 int fHandle;
409 };
joshualittdb0d3ca2014-10-07 12:42:26 -0700410
joshualitt47bb3822014-10-07 16:43:25 -0700411 struct Transform {
412 Transform() : fType(kVoid_GrSLType) { fCurrentValue = SkMatrix::InvalidMatrix(); }
413 ShaderVarHandle fHandle;
414 SkMatrix fCurrentValue;
415 GrSLType fType;
416 };
joshualittdb0d3ca2014-10-07 12:42:26 -0700417
joshualitta5305a12014-10-10 17:47:00 -0700418 SkAutoTDelete<GrGLFragmentProcessor> fGLProc;
419 SkSTArray<2, Transform, true> fTransforms;
joshualitta5305a12014-10-10 17:47:00 -0700420};
joshualittdb0d3ca2014-10-07 12:42:26 -0700421
joshualitta5305a12014-10-10 17:47:00 -0700422struct GrGLInstalledFragProcs : public SkRefCnt {
423 virtual ~GrGLInstalledFragProcs();
424 SkSTArray<8, GrGLInstalledFragProc*, true> fProcs;
joshualitt30ba4362014-08-21 20:18:45 -0700425};
426
joshualitt30ba4362014-08-21 20:18:45 -0700427#endif