blob: 1914adb54ca4408134425003e1fef55455f4f840 [file] [log] [blame]
Brian34ae99d2006-12-18 08:28:54 -07001/*
2 * Mesa 3-D graphics library
Brian5b01c5e2006-12-19 18:02:03 -07003 * Version: 6.5.3
Brian34ae99d2006-12-18 08:28:54 -07004 *
Brian5b01c5e2006-12-19 18:02:03 -07005 * Copyright (C) 2004-2007 Brian Paul All Rights Reserved.
Brian34ae99d2006-12-18 08:28:54 -07006 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
21 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25/**
26 * \file shader_api.c
Brian5b01c5e2006-12-19 18:02:03 -070027 * Implementation of GLSL-related API functions
Brian34ae99d2006-12-18 08:28:54 -070028 * \author Brian Paul
29 */
30
31/**
32 * XXX things to do:
33 * 1. Check that the right error code is generated for all _mesa_error() calls.
Brian5b01c5e2006-12-19 18:02:03 -070034 * 2. Insert FLUSH_VERTICES calls in various places
Brian34ae99d2006-12-18 08:28:54 -070035 */
36
37
38#include "glheader.h"
39#include "context.h"
40#include "hash.h"
Brian34ae99d2006-12-18 08:28:54 -070041#include "program.h"
42#include "prog_parameter.h"
Brian3209c3e2007-01-09 17:49:24 -070043#include "prog_print.h"
44#include "prog_statevars.h"
Brian34ae99d2006-12-18 08:28:54 -070045#include "shader_api.h"
46
47#include "slang_compile.h"
48#include "slang_link.h"
49
50
51
Brianf2923612006-12-20 09:56:44 -070052/**
53 * Allocate a new gl_shader_program object, initialize it.
54 */
55struct gl_shader_program *
56_mesa_new_shader_program(GLcontext *ctx, GLuint name)
57{
58 struct gl_shader_program *shProg;
59 shProg = CALLOC_STRUCT(gl_shader_program);
60 if (shProg) {
61 shProg->Type = GL_SHADER_PROGRAM;
62 shProg->Name = name;
63 shProg->RefCount = 1;
Brian3209c3e2007-01-09 17:49:24 -070064 shProg->Attributes = _mesa_new_parameter_list();
Brianf2923612006-12-20 09:56:44 -070065 }
66 return shProg;
67}
68
69
70void
71_mesa_free_shader_program_data(GLcontext *ctx,
72 struct gl_shader_program *shProg)
73{
74 assert(shProg->Type == GL_SHADER_PROGRAM);
75
76 if (shProg->VertexProgram) {
77 if (shProg->VertexProgram->Base.Parameters == shProg->Uniforms) {
78 /* to prevent a double-free in the next call */
79 shProg->VertexProgram->Base.Parameters = NULL;
80 }
81 _mesa_delete_program(ctx, &shProg->VertexProgram->Base);
82 shProg->VertexProgram = NULL;
83 }
84
85 if (shProg->FragmentProgram) {
86 if (shProg->FragmentProgram->Base.Parameters == shProg->Uniforms) {
87 /* to prevent a double-free in the next call */
88 shProg->FragmentProgram->Base.Parameters = NULL;
89 }
90 _mesa_delete_program(ctx, &shProg->FragmentProgram->Base);
91 shProg->FragmentProgram = NULL;
92 }
93
94
95 if (shProg->Uniforms) {
96 _mesa_free_parameter_list(shProg->Uniforms);
97 shProg->Uniforms = NULL;
98 }
99
100 if (shProg->Varying) {
101 _mesa_free_parameter_list(shProg->Varying);
102 shProg->Varying = NULL;
103 }
104}
105
106
107
108void
109_mesa_free_shader_program(GLcontext *ctx, struct gl_shader_program *shProg)
110{
111 _mesa_free_shader_program_data(ctx, shProg);
112 _mesa_free(shProg);
113}
114
115
116/**
117 * Lookup a GLSL program object.
118 */
119struct gl_shader_program *
120_mesa_lookup_shader_program(GLcontext *ctx, GLuint name)
121{
122 struct gl_shader_program *shProg;
123 if (name) {
124 shProg = (struct gl_shader_program *)
125 _mesa_HashLookup(ctx->Shared->ShaderObjects, name);
126 /* Note that both gl_shader and gl_shader_program objects are kept
127 * in the same hash table. Check the object's type to be sure it's
128 * what we're expecting.
129 */
130 if (shProg && shProg->Type != GL_SHADER_PROGRAM) {
131 return NULL;
132 }
133 return shProg;
134 }
135 return NULL;
136}
137
138
139/**
140 * Allocate a new gl_shader object, initialize it.
141 */
142struct gl_shader *
143_mesa_new_shader(GLcontext *ctx, GLuint name, GLenum type)
144{
145 struct gl_shader *shader;
146 assert(type == GL_FRAGMENT_SHADER || type == GL_VERTEX_SHADER);
147 shader = CALLOC_STRUCT(gl_shader);
148 if (shader) {
149 shader->Type = type;
150 shader->Name = name;
151 shader->RefCount = 1;
152 }
153 return shader;
154}
155
156
157void
158_mesa_free_shader(GLcontext *ctx, struct gl_shader *sh)
159{
160 GLuint i;
161 if (sh->Source)
162 _mesa_free((void *) sh->Source);
163 if (sh->InfoLog)
164 _mesa_free(sh->InfoLog);
165 for (i = 0; i < sh->NumPrograms; i++) {
166 assert(sh->Programs[i]);
167 _mesa_delete_program(ctx, sh->Programs[i]);
168 }
169 if (sh->Programs)
170 _mesa_free(sh->Programs);
171 _mesa_free(sh);
172}
173
174
175/**
176 * Lookup a GLSL shader object.
177 */
178struct gl_shader *
179_mesa_lookup_shader(GLcontext *ctx, GLuint name)
180{
181 if (name) {
182 struct gl_shader *sh = (struct gl_shader *)
183 _mesa_HashLookup(ctx->Shared->ShaderObjects, name);
184 /* Note that both gl_shader and gl_shader_program objects are kept
185 * in the same hash table. Check the object's type to be sure it's
186 * what we're expecting.
187 */
188 if (sh && sh->Type == GL_SHADER_PROGRAM) {
189 assert(sh->Type == GL_VERTEX_SHADER ||
190 sh->Type == GL_FRAGMENT_SHADER);
191 return NULL;
192 }
193 return sh;
194 }
195 return NULL;
196}
197
198
199void
200_mesa_init_shader_state(GLcontext * ctx)
201{
202 ctx->Shader._FragmentShaderPresent = GL_FALSE;
203 ctx->Shader._VertexShaderPresent = GL_FALSE;
204}
205
206
207
Brian34ae99d2006-12-18 08:28:54 -0700208
Brian5b01c5e2006-12-19 18:02:03 -0700209
210/**
211 * Copy string from <src> to <dst>, up to maxLength characters, returning
212 * length of <dst> in <length>.
213 * \param src the strings source
214 * \param maxLength max chars to copy
215 * \param length returns number of chars copied
216 * \param dst the string destination
217 */
218static void
219copy_string(GLchar *dst, GLsizei maxLength, GLsizei *length, const GLchar *src)
220{
221 GLsizei len;
222 for (len = 0; len < maxLength - 1 && src && src[len]; len++)
223 dst[len] = src[len];
224 if (maxLength > 0)
225 dst[len] = 0;
226 if (length)
227 *length = len;
228}
229
230
231
232
233/**
234 * Called via ctx->Driver.AttachShader()
235 */
236void
237_mesa_attach_shader(GLcontext *ctx, GLuint program, GLuint shader)
238{
Brian65a18442006-12-19 18:46:56 -0700239 struct gl_shader_program *shProg
240 = _mesa_lookup_shader_program(ctx, program);
241 struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
242 const GLuint n = shProg->NumShaders;
Brian5b01c5e2006-12-19 18:02:03 -0700243 GLuint i;
244
Brian65a18442006-12-19 18:46:56 -0700245 if (!shProg || !sh) {
Brian43975832007-01-04 08:21:09 -0700246 _mesa_error(ctx, GL_INVALID_VALUE,
Brian5b01c5e2006-12-19 18:02:03 -0700247 "glAttachShader(bad program or shader name)");
248 return;
249 }
250
251 for (i = 0; i < n; i++) {
Brian65a18442006-12-19 18:46:56 -0700252 if (shProg->Shaders[i] == sh) {
Brian5b01c5e2006-12-19 18:02:03 -0700253 /* already attached */
254 return;
Brian34ae99d2006-12-18 08:28:54 -0700255 }
256 }
Brian5b01c5e2006-12-19 18:02:03 -0700257
258 /* grow list */
Brian65a18442006-12-19 18:46:56 -0700259 shProg->Shaders = (struct gl_shader **)
260 _mesa_realloc(shProg->Shaders,
261 n * sizeof(struct gl_shader *),
262 (n + 1) * sizeof(struct gl_shader *));
263 if (!shProg->Shaders) {
Brian5b01c5e2006-12-19 18:02:03 -0700264 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glAttachShader");
265 return;
266 }
267
268 /* append */
Brian65a18442006-12-19 18:46:56 -0700269 shProg->Shaders[n] = sh;
270 sh->RefCount++;
271 shProg->NumShaders++;
Brian5b01c5e2006-12-19 18:02:03 -0700272}
273
274
275void
276_mesa_bind_attrib_location(GLcontext *ctx, GLuint program, GLuint index,
277 const GLchar *name)
278{
Brian65a18442006-12-19 18:46:56 -0700279 struct gl_shader_program *shProg
280 = _mesa_lookup_shader_program(ctx, program);
Brianb7978af2007-01-09 19:17:17 -0700281 const GLint size = -1; /* unknown size */
282 GLint i, oldIndex;
Brian5b01c5e2006-12-19 18:02:03 -0700283
Brian65a18442006-12-19 18:46:56 -0700284 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700285 _mesa_error(ctx, GL_INVALID_VALUE, "glBindAttribLocation(program)");
Brian5b01c5e2006-12-19 18:02:03 -0700286 return;
287 }
288
Brian9e4bae92006-12-20 09:27:42 -0700289 if (!name)
290 return;
291
292 if (strncmp(name, "gl_", 3) == 0) {
293 _mesa_error(ctx, GL_INVALID_OPERATION,
294 "glBindAttribLocation(illegal name)");
295 return;
296 }
297
Brian3209c3e2007-01-09 17:49:24 -0700298 oldIndex = _mesa_get_attrib_location(ctx, program, name);
299
300 /* this will replace the current value if it's already in the list */
Brianb7978af2007-01-09 19:17:17 -0700301 i = _mesa_add_attribute(shProg->Attributes, name, size, index);
Brian3209c3e2007-01-09 17:49:24 -0700302 if (i < 0) {
303 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBindAttribLocation");
304 }
305
306 if (shProg->VertexProgram && oldIndex >= 0) {
307 _slang_remap_attribute(&shProg->VertexProgram->Base, oldIndex, index);
308 }
309
Brian88e2dbf2007-01-10 13:33:38 -0700310#if 0
Brian3209c3e2007-01-09 17:49:24 -0700311 printf("===== post BindAttrib:\n");
312 _mesa_print_program(&shProg->VertexProgram->Base);
Brian88e2dbf2007-01-10 13:33:38 -0700313#endif
Brian34ae99d2006-12-18 08:28:54 -0700314}
315
316
Brian5b01c5e2006-12-19 18:02:03 -0700317GLuint
318_mesa_create_shader(GLcontext *ctx, GLenum type)
319{
Brian65a18442006-12-19 18:46:56 -0700320 struct gl_shader *sh;
Brian5b01c5e2006-12-19 18:02:03 -0700321 GLuint name;
322
323 name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1);
324
325 switch (type) {
Brian65a18442006-12-19 18:46:56 -0700326 case GL_FRAGMENT_SHADER:
327 case GL_VERTEX_SHADER:
328 sh = _mesa_new_shader(ctx, name, type);
Brian5b01c5e2006-12-19 18:02:03 -0700329 break;
330 default:
331 _mesa_error(ctx, GL_INVALID_ENUM, "CreateShader(type)");
332 return 0;
333 }
334
Brian65a18442006-12-19 18:46:56 -0700335 _mesa_HashInsert(ctx->Shared->ShaderObjects, name, sh);
Brian5b01c5e2006-12-19 18:02:03 -0700336
337 return name;
338}
339
340
341GLuint
342_mesa_create_program(GLcontext *ctx)
343{
344 GLuint name;
Brian65a18442006-12-19 18:46:56 -0700345 struct gl_shader_program *shProg;
Brian5b01c5e2006-12-19 18:02:03 -0700346
Brian65a18442006-12-19 18:46:56 -0700347 name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1);
348 shProg = _mesa_new_shader_program(ctx, name);
Brian5b01c5e2006-12-19 18:02:03 -0700349
Brian65a18442006-12-19 18:46:56 -0700350 _mesa_HashInsert(ctx->Shared->ShaderObjects, name, shProg);
Brian5b01c5e2006-12-19 18:02:03 -0700351
352 return name;
353}
354
355
356void
357_mesa_delete_program2(GLcontext *ctx, GLuint name)
358{
Brian65a18442006-12-19 18:46:56 -0700359 struct gl_shader_program *shProg;
Brian5b01c5e2006-12-19 18:02:03 -0700360
Brian65a18442006-12-19 18:46:56 -0700361 shProg = _mesa_lookup_shader_program(ctx, name);
362 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700363 _mesa_error(ctx, GL_INVALID_VALUE, "glDeleteProgram(name)");
Brian5b01c5e2006-12-19 18:02:03 -0700364 return;
365 }
366
Brian9e4bae92006-12-20 09:27:42 -0700367 /* always remove from hash table */
Brian65a18442006-12-19 18:46:56 -0700368 _mesa_HashRemove(ctx->Shared->ShaderObjects, name);
Brian9e4bae92006-12-20 09:27:42 -0700369
370 shProg->DeletePending = GL_TRUE;
371
372 /* decrement refcount, delete if zero */
373 shProg->RefCount--;
374 if (shProg->RefCount <= 0) {
375 _mesa_free_shader_program(ctx, shProg);
376 }
Brian5b01c5e2006-12-19 18:02:03 -0700377}
378
379
380void
381_mesa_delete_shader(GLcontext *ctx, GLuint shader)
382{
Brian9e4bae92006-12-20 09:27:42 -0700383 struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
384 if (!sh) {
385 return;
386 }
Brian5b01c5e2006-12-19 18:02:03 -0700387
Brian9e4bae92006-12-20 09:27:42 -0700388 sh->DeletePending = GL_TRUE;
389 sh->RefCount--;
390 if (sh->RefCount <= 0) {
391 _mesa_free_shader(ctx, sh);
392 }
Brian5b01c5e2006-12-19 18:02:03 -0700393}
394
395
396void
397_mesa_detach_shader(GLcontext *ctx, GLuint program, GLuint shader)
398{
Brian65a18442006-12-19 18:46:56 -0700399 struct gl_shader_program *shProg
400 = _mesa_lookup_shader_program(ctx, program);
401 const GLuint n = shProg->NumShaders;
Brian5b01c5e2006-12-19 18:02:03 -0700402 GLuint i, j;
403
Brian65a18442006-12-19 18:46:56 -0700404 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700405 _mesa_error(ctx, GL_INVALID_VALUE,
Brian5b01c5e2006-12-19 18:02:03 -0700406 "glDetachShader(bad program or shader name)");
407 return;
408 }
409
410 for (i = 0; i < n; i++) {
Brian65a18442006-12-19 18:46:56 -0700411 if (shProg->Shaders[i]->Name == shader) {
412 struct gl_shader **newList;
Brian5b01c5e2006-12-19 18:02:03 -0700413 /* found it */
Brian9e4bae92006-12-20 09:27:42 -0700414
415 shProg->Shaders[i]->RefCount--;
416
Brian5b01c5e2006-12-19 18:02:03 -0700417 /* alloc new, smaller array */
Brian65a18442006-12-19 18:46:56 -0700418 newList = (struct gl_shader **)
419 _mesa_malloc((n - 1) * sizeof(struct gl_shader *));
Brian5b01c5e2006-12-19 18:02:03 -0700420 if (!newList) {
421 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glDetachShader");
422 return;
423 }
424 for (j = 0; j < i; j++) {
Brian65a18442006-12-19 18:46:56 -0700425 newList[j] = shProg->Shaders[j];
Brian5b01c5e2006-12-19 18:02:03 -0700426 }
427 while (++i < n)
Brian65a18442006-12-19 18:46:56 -0700428 newList[j++] = shProg->Shaders[i];
429 _mesa_free(shProg->Shaders);
Brian5b01c5e2006-12-19 18:02:03 -0700430
431 /* XXX refcounting! */
432
Brian65a18442006-12-19 18:46:56 -0700433 shProg->Shaders = newList;
Brian5b01c5e2006-12-19 18:02:03 -0700434 return;
435 }
436 }
437
438 /* not found */
Brian43975832007-01-04 08:21:09 -0700439 _mesa_error(ctx, GL_INVALID_VALUE,
Brian5b01c5e2006-12-19 18:02:03 -0700440 "glDetachShader(shader not found)");
441}
442
443
444void
445_mesa_get_active_attrib(GLcontext *ctx, GLuint program, GLuint index,
446 GLsizei maxLength, GLsizei *length, GLint *size,
447 GLenum *type, GLchar *nameOut)
448{
449 static const GLenum vec_types[] = {
450 GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4
451 };
Brian65a18442006-12-19 18:46:56 -0700452 struct gl_shader_program *shProg
453 = _mesa_lookup_shader_program(ctx, program);
Brian5b01c5e2006-12-19 18:02:03 -0700454 GLint sz;
455
Brian65a18442006-12-19 18:46:56 -0700456 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700457 _mesa_error(ctx, GL_INVALID_VALUE, "glGetActiveUniform");
Brian5b01c5e2006-12-19 18:02:03 -0700458 return;
459 }
460
Brian65a18442006-12-19 18:46:56 -0700461 if (!shProg->Attributes || index >= shProg->Attributes->NumParameters) {
Brian5b01c5e2006-12-19 18:02:03 -0700462 _mesa_error(ctx, GL_INVALID_VALUE, "glGetActiveUniform(index)");
463 return;
464 }
465
466 copy_string(nameOut, maxLength, length,
Brian65a18442006-12-19 18:46:56 -0700467 shProg->Attributes->Parameters[index].Name);
468 sz = shProg->Attributes->Parameters[index].Size;
Brian5b01c5e2006-12-19 18:02:03 -0700469 if (size)
470 *size = sz;
471 if (type)
472 *type = vec_types[sz]; /* XXX this is a temporary hack */
473}
474
475
476/**
477 * Called via ctx->Driver.GetActiveUniform().
478 */
479void
480_mesa_get_active_uniform(GLcontext *ctx, GLuint program, GLuint index,
481 GLsizei maxLength, GLsizei *length, GLint *size,
482 GLenum *type, GLchar *nameOut)
483{
484 static const GLenum vec_types[] = {
485 GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4
486 };
Brian65a18442006-12-19 18:46:56 -0700487 struct gl_shader_program *shProg
488 = _mesa_lookup_shader_program(ctx, program);
Brian5b01c5e2006-12-19 18:02:03 -0700489 GLint sz;
490
Brian65a18442006-12-19 18:46:56 -0700491 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700492 _mesa_error(ctx, GL_INVALID_VALUE, "glGetActiveUniform");
Brian5b01c5e2006-12-19 18:02:03 -0700493 return;
494 }
495
Brian65a18442006-12-19 18:46:56 -0700496 if (!shProg->Uniforms || index >= shProg->Uniforms->NumParameters) {
Brian5b01c5e2006-12-19 18:02:03 -0700497 _mesa_error(ctx, GL_INVALID_VALUE, "glGetActiveUniform(index)");
498 return;
499 }
500
501 copy_string(nameOut, maxLength, length,
Brian65a18442006-12-19 18:46:56 -0700502 shProg->Uniforms->Parameters[index].Name);
503 sz = shProg->Uniforms->Parameters[index].Size;
Brian5b01c5e2006-12-19 18:02:03 -0700504 if (size)
505 *size = sz;
506 if (type)
507 *type = vec_types[sz]; /* XXX this is a temporary hack */
508}
509
510
511/**
512 * Called via ctx->Driver.GetAttachedShaders().
513 */
514void
515_mesa_get_attached_shaders(GLcontext *ctx, GLuint program, GLsizei maxCount,
516 GLsizei *count, GLuint *obj)
517{
Brian65a18442006-12-19 18:46:56 -0700518 struct gl_shader_program *shProg
519 = _mesa_lookup_shader_program(ctx, program);
520 if (shProg) {
Brian223d7cb2007-01-23 16:37:51 -0700521 GLint i;
Brian65a18442006-12-19 18:46:56 -0700522 for (i = 0; i < maxCount && i < shProg->NumShaders; i++) {
523 obj[i] = shProg->Shaders[i]->Name;
Brian5b01c5e2006-12-19 18:02:03 -0700524 }
525 if (count)
526 *count = i;
527 }
528 else {
Brian43975832007-01-04 08:21:09 -0700529 _mesa_error(ctx, GL_INVALID_VALUE, "glGetAttachedShaders");
Brian5b01c5e2006-12-19 18:02:03 -0700530 }
531}
532
533
534GLint
535_mesa_get_attrib_location(GLcontext *ctx, GLuint program,
536 const GLchar *name)
537{
Brian65a18442006-12-19 18:46:56 -0700538 struct gl_shader_program *shProg
539 = _mesa_lookup_shader_program(ctx, program);
Brian5b01c5e2006-12-19 18:02:03 -0700540
Brian65a18442006-12-19 18:46:56 -0700541 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700542 _mesa_error(ctx, GL_INVALID_VALUE, "glGetAttribLocation");
Brian5b01c5e2006-12-19 18:02:03 -0700543 return -1;
544 }
545
Brian65a18442006-12-19 18:46:56 -0700546 if (!shProg->LinkStatus) {
Brian5b01c5e2006-12-19 18:02:03 -0700547 _mesa_error(ctx, GL_INVALID_OPERATION,
548 "glGetAttribLocation(program not linked)");
549 return -1;
550 }
551
552 if (!name)
553 return -1;
554
Brian65a18442006-12-19 18:46:56 -0700555 if (shProg->Attributes) {
Brian3209c3e2007-01-09 17:49:24 -0700556 GLint i = _mesa_lookup_parameter_index(shProg->Attributes, -1, name);
557 if (i >= 0) {
Brian01a91eb2007-01-09 19:26:22 -0700558 return shProg->Attributes->Parameters[i].StateIndexes[0];
Brian5b01c5e2006-12-19 18:02:03 -0700559 }
560 }
561 return -1;
562}
563
564
565GLuint
566_mesa_get_handle(GLcontext *ctx, GLenum pname)
Brian34ae99d2006-12-18 08:28:54 -0700567{
568#if 0
569 GET_CURRENT_CONTEXT(ctx);
570
571 switch (pname) {
572 case GL_PROGRAM_OBJECT_ARB:
573 {
Brian5b01c5e2006-12-19 18:02:03 -0700574 struct gl2_program_intf **pro = ctx->Shader.CurrentProgram;
Brian34ae99d2006-12-18 08:28:54 -0700575
576 if (pro != NULL)
577 return (**pro)._container._generic.
578 GetName((struct gl2_generic_intf **) (pro));
579 }
580 break;
581 default:
582 _mesa_error(ctx, GL_INVALID_ENUM, "glGetHandleARB");
583 }
584#endif
585 return 0;
586}
587
588
Brian5b01c5e2006-12-19 18:02:03 -0700589void
590_mesa_get_programiv(GLcontext *ctx, GLuint program,
591 GLenum pname, GLint *params)
Brian34ae99d2006-12-18 08:28:54 -0700592{
Brian65a18442006-12-19 18:46:56 -0700593 struct gl_shader_program *shProg
594 = _mesa_lookup_shader_program(ctx, program);
Brian34ae99d2006-12-18 08:28:54 -0700595
Brian65a18442006-12-19 18:46:56 -0700596 if (!shProg) {
Brian5b01c5e2006-12-19 18:02:03 -0700597 _mesa_error(ctx, GL_INVALID_VALUE, "glGetProgramiv(program)");
Brian34ae99d2006-12-18 08:28:54 -0700598 return;
599 }
600
Brian5b01c5e2006-12-19 18:02:03 -0700601 switch (pname) {
602 case GL_DELETE_STATUS:
Brian65a18442006-12-19 18:46:56 -0700603 *params = shProg->DeletePending;
Brian5b01c5e2006-12-19 18:02:03 -0700604 break;
605 case GL_LINK_STATUS:
Brian65a18442006-12-19 18:46:56 -0700606 *params = shProg->LinkStatus;
Brian34ae99d2006-12-18 08:28:54 -0700607 break;
Brian5b01c5e2006-12-19 18:02:03 -0700608 case GL_VALIDATE_STATUS:
Brian65a18442006-12-19 18:46:56 -0700609 *params = shProg->Validated;
Brian5b01c5e2006-12-19 18:02:03 -0700610 break;
611 case GL_INFO_LOG_LENGTH:
Brian65a18442006-12-19 18:46:56 -0700612 *params = shProg->InfoLog ? strlen(shProg->InfoLog) : 0;
Brian5b01c5e2006-12-19 18:02:03 -0700613 break;
614 case GL_ATTACHED_SHADERS:
Brian65a18442006-12-19 18:46:56 -0700615 *params = shProg->NumShaders;
Brian5b01c5e2006-12-19 18:02:03 -0700616 break;
617 case GL_ACTIVE_ATTRIBUTES:
Brian3209c3e2007-01-09 17:49:24 -0700618 *params = shProg->Attributes ? shProg->Attributes->NumParameters : 0;
Brian5b01c5e2006-12-19 18:02:03 -0700619 break;
620 case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
Brian65a18442006-12-19 18:46:56 -0700621 *params = _mesa_parameter_longest_name(shProg->Attributes);
Brian5b01c5e2006-12-19 18:02:03 -0700622 break;
623 case GL_ACTIVE_UNIFORMS:
Brian65a18442006-12-19 18:46:56 -0700624 *params = shProg->Uniforms ? shProg->Uniforms->NumParameters : 0;
Brian5b01c5e2006-12-19 18:02:03 -0700625 break;
626 case GL_ACTIVE_UNIFORM_MAX_LENGTH:
Brian65a18442006-12-19 18:46:56 -0700627 *params = _mesa_parameter_longest_name(shProg->Uniforms);
Brian34ae99d2006-12-18 08:28:54 -0700628 break;
629 default:
Brian5b01c5e2006-12-19 18:02:03 -0700630 _mesa_error(ctx, GL_INVALID_ENUM, "glGetProgramiv(pname)");
631 return;
Brian34ae99d2006-12-18 08:28:54 -0700632 }
Brian5b01c5e2006-12-19 18:02:03 -0700633}
Brian34ae99d2006-12-18 08:28:54 -0700634
Brian34ae99d2006-12-18 08:28:54 -0700635
Brian5b01c5e2006-12-19 18:02:03 -0700636void
637_mesa_get_shaderiv(GLcontext *ctx, GLuint name, GLenum pname, GLint *params)
638{
Brian65a18442006-12-19 18:46:56 -0700639 struct gl_shader *shader = _mesa_lookup_shader(ctx, name);
Brian5b01c5e2006-12-19 18:02:03 -0700640
641 if (!shader) {
642 _mesa_error(ctx, GL_INVALID_VALUE, "glGetShaderiv(shader)");
643 return;
644 }
Brian65a18442006-12-19 18:46:56 -0700645
Brian5b01c5e2006-12-19 18:02:03 -0700646 switch (pname) {
647 case GL_SHADER_TYPE:
648 *params = shader->Type;
649 break;
650 case GL_DELETE_STATUS:
651 *params = shader->DeletePending;
652 break;
653 case GL_COMPILE_STATUS:
654 *params = shader->CompileStatus;
655 break;
656 case GL_INFO_LOG_LENGTH:
657 *params = shader->InfoLog ? strlen(shader->InfoLog) : 0;
658 break;
659 case GL_SHADER_SOURCE_LENGTH:
660 *params = shader->Source ? strlen((char *) shader->Source) : 0;
661 break;
662 default:
663 _mesa_error(ctx, GL_INVALID_ENUM, "glGetShaderiv(pname)");
664 return;
665 }
666}
667
668
669void
670_mesa_get_program_info_log(GLcontext *ctx, GLuint program, GLsizei bufSize,
671 GLsizei *length, GLchar *infoLog)
672{
Brian65a18442006-12-19 18:46:56 -0700673 struct gl_shader_program *shProg
674 = _mesa_lookup_shader_program(ctx, program);
675 if (!shProg) {
Brian5b01c5e2006-12-19 18:02:03 -0700676 _mesa_error(ctx, GL_INVALID_VALUE, "glGetProgramInfoLog(program)");
677 return;
678 }
Brian65a18442006-12-19 18:46:56 -0700679 copy_string(infoLog, bufSize, length, shProg->InfoLog);
Brian5b01c5e2006-12-19 18:02:03 -0700680}
681
682
683void
684_mesa_get_shader_info_log(GLcontext *ctx, GLuint shader, GLsizei bufSize,
685 GLsizei *length, GLchar *infoLog)
686{
Brian65a18442006-12-19 18:46:56 -0700687 struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
688 if (!sh) {
Brian5b01c5e2006-12-19 18:02:03 -0700689 _mesa_error(ctx, GL_INVALID_VALUE, "glGetShaderInfoLog(shader)");
690 return;
691 }
Brian65a18442006-12-19 18:46:56 -0700692 copy_string(infoLog, bufSize, length, sh->InfoLog);
Brian5b01c5e2006-12-19 18:02:03 -0700693}
694
695
696/**
697 * Called via ctx->Driver.GetShaderSource().
698 */
699void
700_mesa_get_shader_source(GLcontext *ctx, GLuint shader, GLsizei maxLength,
701 GLsizei *length, GLchar *sourceOut)
702{
Brian65a18442006-12-19 18:46:56 -0700703 struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
704 if (!sh) {
Brian5b01c5e2006-12-19 18:02:03 -0700705 _mesa_error(ctx, GL_INVALID_VALUE, "glGetShaderSource(shader)");
706 return;
707 }
Brian65a18442006-12-19 18:46:56 -0700708 copy_string(sourceOut, maxLength, length, sh->Source);
Brian5b01c5e2006-12-19 18:02:03 -0700709}
710
711
712/**
713 * Called via ctx->Driver.GetUniformfv().
714 */
715void
716_mesa_get_uniformfv(GLcontext *ctx, GLuint program, GLint location,
717 GLfloat *params)
718{
Brian65a18442006-12-19 18:46:56 -0700719 struct gl_shader_program *shProg
720 = _mesa_lookup_shader_program(ctx, program);
721 if (shProg) {
Brian223d7cb2007-01-23 16:37:51 -0700722 GLint i;
Brian65a18442006-12-19 18:46:56 -0700723 if (location >= 0 && location < shProg->Uniforms->NumParameters) {
724 for (i = 0; i < shProg->Uniforms->Parameters[location].Size; i++) {
725 params[i] = shProg->Uniforms->ParameterValues[location][i];
Brian5b01c5e2006-12-19 18:02:03 -0700726 }
727 }
728 else {
729 _mesa_error(ctx, GL_INVALID_VALUE, "glGetUniformfv(location)");
730 }
731 }
732 else {
Brian43975832007-01-04 08:21:09 -0700733 _mesa_error(ctx, GL_INVALID_VALUE, "glGetUniformfv(program)");
Brian5b01c5e2006-12-19 18:02:03 -0700734 }
735}
736
737
738/**
739 * Called via ctx->Driver.GetUniformLocation().
740 */
741GLint
742_mesa_get_uniform_location(GLcontext *ctx, GLuint program, const GLchar *name)
743{
Brian71623982007-01-30 16:55:03 -0700744 struct gl_shader_program *shProg
745 = _mesa_lookup_shader_program(ctx, program);
746 if (shProg) {
Brian5b01c5e2006-12-19 18:02:03 -0700747 GLuint loc;
Brian65a18442006-12-19 18:46:56 -0700748 for (loc = 0; loc < shProg->Uniforms->NumParameters; loc++) {
Brian5b01c5e2006-12-19 18:02:03 -0700749 const struct gl_program_parameter *u
Brian65a18442006-12-19 18:46:56 -0700750 = shProg->Uniforms->Parameters + loc;
Brian29053852006-12-21 11:21:26 -0700751 /* XXX this is a temporary simplification / short-cut.
752 * We need to handle things like "e.c[0].b" as seen in the
753 * GLSL orange book, page 189.
754 */
Brian5cf73262007-01-05 16:02:45 -0700755 if ((u->Type == PROGRAM_UNIFORM ||
756 u->Type == PROGRAM_SAMPLER) && !strcmp(u->Name, name)) {
Brian5b01c5e2006-12-19 18:02:03 -0700757 return loc;
758 }
759 }
760 }
761 return -1;
762
763}
764
765
766GLboolean
767_mesa_is_program(GLcontext *ctx, GLuint name)
768{
Brian65a18442006-12-19 18:46:56 -0700769 struct gl_shader_program *shProg = _mesa_lookup_shader_program(ctx, name);
770 return shProg ? GL_TRUE : GL_FALSE;
Brian5b01c5e2006-12-19 18:02:03 -0700771}
772
773
774GLboolean
775_mesa_is_shader(GLcontext *ctx, GLuint name)
776{
Brian65a18442006-12-19 18:46:56 -0700777 struct gl_shader *shader = _mesa_lookup_shader(ctx, name);
Brian5b01c5e2006-12-19 18:02:03 -0700778 return shader ? GL_TRUE : GL_FALSE;
Brian34ae99d2006-12-18 08:28:54 -0700779}
780
781
782
Brian5b01c5e2006-12-19 18:02:03 -0700783/**
784 * Called via ctx->Driver.ShaderSource()
785 */
786void
787_mesa_shader_source(GLcontext *ctx, GLuint shader, const GLchar *source)
Brian34ae99d2006-12-18 08:28:54 -0700788{
Brian65a18442006-12-19 18:46:56 -0700789 struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
790 if (!sh) {
Brian5b01c5e2006-12-19 18:02:03 -0700791 _mesa_error(ctx, GL_INVALID_VALUE, "glShaderSource(shaderObj)");
Brian34ae99d2006-12-18 08:28:54 -0700792 return;
793 }
794
Brian34ae99d2006-12-18 08:28:54 -0700795 /* free old shader source string and install new one */
Brian65a18442006-12-19 18:46:56 -0700796 if (sh->Source) {
797 _mesa_free((void *) sh->Source);
Brian34ae99d2006-12-18 08:28:54 -0700798 }
Brian65a18442006-12-19 18:46:56 -0700799 sh->Source = source;
Brian9e4bae92006-12-20 09:27:42 -0700800 sh->CompileStatus = GL_FALSE;
Brian34ae99d2006-12-18 08:28:54 -0700801}
802
803
Brian5b01c5e2006-12-19 18:02:03 -0700804/**
805 * Called via ctx->Driver.CompileShader()
806 */
807void
808_mesa_compile_shader(GLcontext *ctx, GLuint shaderObj)
Brian34ae99d2006-12-18 08:28:54 -0700809{
Brian65a18442006-12-19 18:46:56 -0700810 struct gl_shader *sh = _mesa_lookup_shader(ctx, shaderObj);
Brian34ae99d2006-12-18 08:28:54 -0700811
Brian65a18442006-12-19 18:46:56 -0700812 if (!sh) {
Brian34ae99d2006-12-18 08:28:54 -0700813 _mesa_error(ctx, GL_INVALID_VALUE, "glCompileShader(shaderObj)");
814 return;
815 }
816
Brian43975832007-01-04 08:21:09 -0700817 sh->CompileStatus = _slang_compile(ctx, sh);
Brian34ae99d2006-12-18 08:28:54 -0700818}
819
820
Brian5b01c5e2006-12-19 18:02:03 -0700821/**
822 * Called via ctx->Driver.LinkProgram()
823 */
824void
825_mesa_link_program(GLcontext *ctx, GLuint program)
Brian34ae99d2006-12-18 08:28:54 -0700826{
Brian65a18442006-12-19 18:46:56 -0700827 struct gl_shader_program *shProg;
Brian34ae99d2006-12-18 08:28:54 -0700828
Brian65a18442006-12-19 18:46:56 -0700829 shProg = _mesa_lookup_shader_program(ctx, program);
830 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700831 _mesa_error(ctx, GL_INVALID_VALUE, "glLinkProgram(program)");
Brian34ae99d2006-12-18 08:28:54 -0700832 return;
833 }
834
Brian65a18442006-12-19 18:46:56 -0700835 _slang_link2(ctx, program, shProg);
Brian34ae99d2006-12-18 08:28:54 -0700836}
837
838
839/**
Brian5b01c5e2006-12-19 18:02:03 -0700840 * Called via ctx->Driver.UseProgram()
Brian34ae99d2006-12-18 08:28:54 -0700841 */
Brian5b01c5e2006-12-19 18:02:03 -0700842void
843_mesa_use_program(GLcontext *ctx, GLuint program)
Brian34ae99d2006-12-18 08:28:54 -0700844{
Brian9e4bae92006-12-20 09:27:42 -0700845 /* unbind old */
846 if (ctx->Shader.CurrentProgram) {
847 ctx->Shader.CurrentProgram->RefCount--;
848 if (ctx->Shader.CurrentProgram->RefCount <= 0) {
849 _mesa_free_shader_program(ctx, ctx->Shader.CurrentProgram);
850 }
851 ctx->Shader.CurrentProgram = NULL;
852 }
853
Brian5b01c5e2006-12-19 18:02:03 -0700854 if (program) {
Brian65a18442006-12-19 18:46:56 -0700855 struct gl_shader_program *shProg;
856 shProg = _mesa_lookup_shader_program(ctx, program);
857 if (!shProg) {
Brian43975832007-01-04 08:21:09 -0700858 _mesa_error(ctx, GL_INVALID_VALUE,
Brian5b01c5e2006-12-19 18:02:03 -0700859 "glUseProgramObjectARB(programObj)");
860 return;
861 }
Brian65a18442006-12-19 18:46:56 -0700862 ctx->Shader.CurrentProgram = shProg;
Brian9e4bae92006-12-20 09:27:42 -0700863 shProg->RefCount++;
Brian5b01c5e2006-12-19 18:02:03 -0700864 }
865 else {
866 /* don't use a shader program */
867 ctx->Shader.CurrentProgram = NULL;
868 }
869}
Brian34ae99d2006-12-18 08:28:54 -0700870
Brian5b01c5e2006-12-19 18:02:03 -0700871
872/**
873 * Called via ctx->Driver.Uniform().
874 */
875void
876_mesa_uniform(GLcontext *ctx, GLint location, GLsizei count,
877 const GLvoid *values, GLenum type)
878{
Brian3a8e2772006-12-20 17:19:16 -0700879 struct gl_shader_program *shProg = ctx->Shader.CurrentProgram;
Brian89dc4852007-01-04 14:35:44 -0700880 GLfloat *uniformVal;
Brian3a8e2772006-12-20 17:19:16 -0700881
882 if (!shProg || !shProg->LinkStatus) {
Brian5b01c5e2006-12-19 18:02:03 -0700883 _mesa_error(ctx, GL_INVALID_OPERATION, "glUniform(program not linked)");
Brian3a8e2772006-12-20 17:19:16 -0700884 return;
885 }
886
Brian223d7cb2007-01-23 16:37:51 -0700887 if (location < 0 || location >= (GLint) shProg->Uniforms->NumParameters) {
Brian3a8e2772006-12-20 17:19:16 -0700888 _mesa_error(ctx, GL_INVALID_VALUE, "glUniform(location)");
889 return;
890 }
891
892 FLUSH_VERTICES(ctx, _NEW_PROGRAM);
893
Brianfee9bbe2007-02-02 18:05:43 -0700894 /*
895 * If we're setting a sampler, we must use glUniformi1()!
896 */
897 if (shProg->Uniforms->Parameters[location].Type == PROGRAM_SAMPLER) {
898 if (type != GL_INT || count != 1) {
899 _mesa_error(ctx, GL_INVALID_OPERATION,
900 "glUniform(only glUniform1i can be used "
901 "to set sampler uniforms)");
902 return;
903 }
904 }
905
Brian89dc4852007-01-04 14:35:44 -0700906 uniformVal = shProg->Uniforms->ParameterValues[location];
Brian3a8e2772006-12-20 17:19:16 -0700907
Brianfee9bbe2007-02-02 18:05:43 -0700908 /* XXX obey 'count' parameter! */
909
Brian89dc4852007-01-04 14:35:44 -0700910 if (type == GL_INT ||
911 type == GL_INT_VEC2 ||
912 type == GL_INT_VEC3 ||
913 type == GL_INT_VEC4) {
914 const GLint *iValues = (const GLint *) values;
915 switch (type) {
916 case GL_INT_VEC4:
917 uniformVal[3] = (GLfloat) iValues[3];
918 /* fall-through */
919 case GL_INT_VEC3:
920 uniformVal[2] = (GLfloat) iValues[2];
921 /* fall-through */
922 case GL_INT_VEC2:
923 uniformVal[1] = (GLfloat) iValues[1];
924 /* fall-through */
925 case GL_INT:
926 uniformVal[0] = (GLfloat) iValues[0];
927 break;
928 default:
929 _mesa_problem(ctx, "Invalid type in _mesa_uniform");
930 return;
931 }
932 }
933 else {
Brian8a48f352007-01-10 12:18:50 -0700934 const GLfloat *fValues = (const GLfloat *) values;
Brian89dc4852007-01-04 14:35:44 -0700935 switch (type) {
936 case GL_FLOAT_VEC4:
937 uniformVal[3] = fValues[3];
938 /* fall-through */
939 case GL_FLOAT_VEC3:
940 uniformVal[2] = fValues[2];
941 /* fall-through */
942 case GL_FLOAT_VEC2:
943 uniformVal[1] = fValues[1];
944 /* fall-through */
945 case GL_FLOAT:
946 uniformVal[0] = fValues[0];
947 break;
948 default:
949 _mesa_problem(ctx, "Invalid type in _mesa_uniform");
950 return;
951 }
Brian5b01c5e2006-12-19 18:02:03 -0700952 }
Brian5cf73262007-01-05 16:02:45 -0700953
954 if (shProg->Uniforms->Parameters[location].Type == PROGRAM_SAMPLER) {
955 _slang_resolve_samplers(shProg, &shProg->VertexProgram->Base);
956 _slang_resolve_samplers(shProg, &shProg->FragmentProgram->Base);
957 FLUSH_VERTICES(ctx, _NEW_TEXTURE);
958 }
Brian34ae99d2006-12-18 08:28:54 -0700959}
960
961
962/**
Brian5b01c5e2006-12-19 18:02:03 -0700963 * Called by ctx->Driver.UniformMatrix().
Brian34ae99d2006-12-18 08:28:54 -0700964 */
Brian5b01c5e2006-12-19 18:02:03 -0700965void
966_mesa_uniform_matrix(GLcontext *ctx, GLint cols, GLint rows,
967 GLenum matrixType, GLint location, GLsizei count,
968 GLboolean transpose, const GLfloat *values)
Brian34ae99d2006-12-18 08:28:54 -0700969{
Brian3a8e2772006-12-20 17:19:16 -0700970 struct gl_shader_program *shProg = ctx->Shader.CurrentProgram;
971 if (!shProg || !shProg->LinkStatus) {
972 _mesa_error(ctx, GL_INVALID_OPERATION,
973 "glUniformMatrix(program not linked)");
974 return;
975 }
976 if (location < 0 || location >= shProg->Uniforms->NumParameters) {
977 _mesa_error(ctx, GL_INVALID_VALUE, "glUniformMatrix(location)");
978 return;
979 }
Brian34ae99d2006-12-18 08:28:54 -0700980 if (values == NULL) {
Brian3a8e2772006-12-20 17:19:16 -0700981 _mesa_error(ctx, GL_INVALID_VALUE, "glUniformMatrix");
Brian34ae99d2006-12-18 08:28:54 -0700982 return;
983 }
984
985 FLUSH_VERTICES(ctx, _NEW_PROGRAM);
986
Brian3a8e2772006-12-20 17:19:16 -0700987 /*
988 * Note: the _columns_ of a matrix are stored in program registers, not
989 * the rows.
990 */
991 /* XXXX need to test 3x3 and 2x2 matrices... */
Brian34ae99d2006-12-18 08:28:54 -0700992 if (transpose) {
Brian3a8e2772006-12-20 17:19:16 -0700993 GLuint row, col;
994 for (col = 0; col < cols; col++) {
995 GLfloat *v = shProg->Uniforms->ParameterValues[location + col];
996 for (row = 0; row < rows; row++) {
997 v[row] = values[col * rows + row];
Brian34ae99d2006-12-18 08:28:54 -0700998 }
Brian34ae99d2006-12-18 08:28:54 -0700999 }
Brian34ae99d2006-12-18 08:28:54 -07001000 }
1001 else {
Brian3a8e2772006-12-20 17:19:16 -07001002 GLuint row, col;
1003 for (col = 0; col < cols; col++) {
1004 GLfloat *v = shProg->Uniforms->ParameterValues[location + col];
1005 for (row = 0; row < rows; row++) {
1006 v[row] = values[row * cols + col];
1007 }
1008 }
Brian34ae99d2006-12-18 08:28:54 -07001009 }
1010}
1011
1012
Brian5b01c5e2006-12-19 18:02:03 -07001013void
1014_mesa_validate_program(GLcontext *ctx, GLuint program)
Brian34ae99d2006-12-18 08:28:54 -07001015{
Brian65a18442006-12-19 18:46:56 -07001016 struct gl_shader_program *shProg;
1017 shProg = _mesa_lookup_shader_program(ctx, program);
1018 if (!shProg) {
Brian43975832007-01-04 08:21:09 -07001019 _mesa_error(ctx, GL_INVALID_VALUE, "glValidateProgram(program)");
Brian34ae99d2006-12-18 08:28:54 -07001020 return;
1021 }
Brian5b01c5e2006-12-19 18:02:03 -07001022 /* XXX temporary */
Brian65a18442006-12-19 18:46:56 -07001023 shProg->Validated = GL_TRUE;
Brian34ae99d2006-12-18 08:28:54 -07001024
Brian5b01c5e2006-12-19 18:02:03 -07001025 /* From the GL spec:
1026 any two active samplers in the current program object are of
1027 different types, but refer to the same texture image unit,
1028
1029 any active sampler in the current program object refers to a texture
1030 image unit where fixed-function fragment processing accesses a
1031 texture target that does not match the sampler type, or
1032
1033 the sum of the number of active samplers in the program and the
1034 number of texture image units enabled for fixed-function fragment
1035 processing exceeds the combined limit on the total number of texture
1036 image units allowed.
1037 */
Brian34ae99d2006-12-18 08:28:54 -07001038}