1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 //
21 // rf_program.c
22 // Vertex and fragment program handling
23 //
24
25 #include "rf_local.h"
26
27 #define MAX_PROGRAMS 256
28 #define MAX_PROGRAM_HASH (MAX_PROGRAMS/4)
29
30 static program_t r_programList[MAX_PROGRAMS];
31 static program_t *r_programHashTree[MAX_PROGRAM_HASH];
32 static uint32 r_numPrograms;
33
34 static uint32 r_numProgramErrors;
35 static uint32 r_numProgramWarnings;
36
37 /*
38 ==================
39 Program_Printf
40 ==================
41 */
Program_Printf(comPrint_t flags,char * fmt,...)42 static void Program_Printf (comPrint_t flags, char *fmt, ...)
43 {
44 va_list argptr;
45 char msg[MAX_COMPRINT];
46
47 if (flags & PRNT_ERROR)
48 r_numProgramErrors++;
49 else if (flags & PRNT_WARNING)
50 r_numProgramWarnings++;
51
52 // Evaluate args
53 va_start (argptr, fmt);
54 vsnprintf (msg, sizeof (msg), fmt, argptr);
55 va_end (argptr);
56
57 // Print
58 Com_ConPrint (flags, msg);
59 }
60
61
62 /*
63 ==================
64 Program_DevPrintf
65 ==================
66 */
Program_DevPrintf(comPrint_t flags,char * fmt,...)67 static void Program_DevPrintf (comPrint_t flags, char *fmt, ...)
68 {
69 va_list argptr;
70 char msg[MAX_COMPRINT];
71
72 if (!developer->intVal)
73 return;
74
75 if (flags & PRNT_ERROR)
76 r_numProgramErrors++;
77 else if (flags & PRNT_WARNING)
78 r_numProgramWarnings++;
79
80 // Evaluate args
81 va_start (argptr, fmt);
82 vsnprintf (msg, sizeof (msg), fmt, argptr);
83 va_end (argptr);
84
85 // Print
86 Com_ConPrint (flags, msg);
87 }
88
89 /*
90 ==============================================================================
91
92 PROGRAM UPLOADING
93
94 ==============================================================================
95 */
96
97 /*
98 ===============
99 R_UploadProgram
100 ===============
101 */
R_UploadProgram(char * name,GLenum target,const char * buffer,GLsizei bufferLen,GLuint * progNum,GLint * upInstructions,GLint * upNative)102 static qBool R_UploadProgram (char *name, GLenum target, const char *buffer, GLsizei bufferLen, GLuint *progNum, GLint *upInstructions, GLint *upNative)
103 {
104 const byte *errorString;
105 int errorPos;
106
107 // Generate a progNum
108 qglGenProgramsARB (1, progNum);
109 qglBindProgramARB (target, *progNum);
110 if (!*progNum) {
111 Program_Printf (PRNT_ERROR, "R_UploadProgram: could not allocate a progNum!\n");
112 return qFalse;
113 }
114
115 // Upload
116 qglProgramStringARB (target, GL_PROGRAM_FORMAT_ASCII_ARB, bufferLen, buffer);
117
118 // Check for errors
119 qglGetIntegerv (GL_PROGRAM_ERROR_POSITION_ARB, &errorPos);
120 if (errorPos != -1) {
121 // Error thrown
122 errorString = qglGetString (GL_PROGRAM_ERROR_STRING_ARB);
123 switch (target) {
124 case GL_VERTEX_PROGRAM_ARB:
125 if (errorPos == bufferLen) {
126 Program_Printf (PRNT_ERROR, "R_UploadProgram: '%s' vertex program error at EOF\n", name);
127 }
128 else {
129 Program_Printf (PRNT_ERROR, "R_UploadProgram: '%s' vertex program error\n", name);
130 Program_Printf (PRNT_ERROR, "GL_PROGRAM_ERROR_POSITION: %i\n", errorPos);
131 }
132 break;
133
134 case GL_FRAGMENT_PROGRAM_ARB:
135 if (errorPos == bufferLen) {
136 Program_Printf (PRNT_ERROR, "R_UploadProgram: '%s' fragment program error at EOF\n", name);
137 }
138 else {
139 Program_Printf (PRNT_ERROR, "R_UploadProgram: '%s' fragment program error\n", name);
140 Program_Printf (PRNT_ERROR, "GL_PROGRAM_ERROR_POSITION: %i\n", errorPos);
141 }
142 break;
143 }
144 Program_Printf (PRNT_ERROR, "GL_PROGRAM_ERROR_STRING: %s\n", errorString);
145
146 qglDeleteProgramsARB (1, progNum);
147 return qFalse;
148 }
149
150 qglGetProgramivARB (target, GL_PROGRAM_INSTRUCTIONS_ARB, upInstructions);
151 qglGetProgramivARB (target, GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB, upNative);
152 return qTrue;
153 }
154
155 /*
156 ===============
157 R_LoadProgram
158 ===============
159 */
R_LoadProgram(char * name,qBool baseDir,GLenum target,const char * buffer,GLsizei bufferLen)160 static program_t *R_LoadProgram (char *name, qBool baseDir, GLenum target, const char *buffer, GLsizei bufferLen)
161 {
162 char fixedName[MAX_QPATH];
163 program_t *prog;
164 uint32 i;
165 GLuint progNum;
166 GLint upInstructions, upNative;
167
168 // Normalize the name
169 Com_NormalizePath (fixedName, sizeof (fixedName), name);
170 Q_strlwr (fixedName);
171
172 // Upload the program
173 if (!R_UploadProgram (fixedName, target, buffer, bufferLen, &progNum, &upInstructions, &upNative))
174 return NULL;
175
176 // Find a free r_programList spot
177 for (i=0, prog=r_programList ; i<r_numPrograms ; i++, prog++) {
178 if (!prog->progNum)
179 break;
180 }
181
182 // None found, create a new spot
183 if (i == r_numPrograms) {
184 if (r_numPrograms+1 >= MAX_PROGRAMS)
185 Com_Error (ERR_DROP, "R_LoadProgram: r_numPrograms >= MAX_PROGRAMS");
186
187 prog = &r_programList[r_numPrograms++];
188 }
189
190 // Fill out properties
191 Q_strncpyz (prog->name, fixedName, sizeof (prog->name));
192 prog->hashValue = Com_HashGenericFast (prog->name, MAX_PROGRAM_HASH);
193 prog->baseDir = baseDir;
194 prog->target = target;
195 prog->upInstructions = upInstructions;
196 prog->upNative = upNative;
197
198 // Link it in
199 prog->hashNext = r_programHashTree[prog->hashValue];
200 r_programHashTree[prog->hashValue] = prog;
201 return prog;
202 }
203
204 /*
205 ==============================================================================
206
207 REGISTRATION
208
209 ==============================================================================
210 */
211
212 /*
213 ===============
214 R_RegisterProgram
215 ===============
216 */
R_RegisterProgram(char * name,qBool fragProg)217 program_t *R_RegisterProgram (char *name, qBool fragProg)
218 {
219 char fixedName[MAX_QPATH];
220 program_t *prog, *best;
221 GLenum target;
222 uint32 hash;
223
224 // Check the name
225 if (!name || !name[0])
226 return NULL;
227
228 // Check for extension
229 if (fragProg) {
230 if (!ri.config.extFragmentProgram) {
231 Com_Error (ERR_DROP, "R_RegisterProgram: attempted to register fragment program when extension is not enabled");
232 return NULL;
233 }
234 }
235 else if (!ri.config.extVertexProgram) {
236 Com_Error (ERR_DROP, "R_RegisterProgram: attempted to register vertex program when extension is not enabled");
237 return NULL;
238 }
239
240 // Set the target
241 if (fragProg)
242 target = GL_FRAGMENT_PROGRAM_ARB;
243 else
244 target = GL_VERTEX_PROGRAM_ARB;
245
246 // Fix the name
247 Com_NormalizePath (fixedName, sizeof (fixedName), name);
248 Q_strlwr (fixedName);
249
250 // Calculate hash
251 hash = Com_HashGenericFast (fixedName, MAX_PROGRAM_HASH);
252
253 // Search
254 best = NULL;
255 for (prog=r_programHashTree[hash] ; prog ; prog=prog->hashNext) {
256 if (prog->target != target)
257 continue;
258 if (strcmp (fixedName, prog->name))
259 continue;
260
261 if (!best || prog->baseDir >= best->baseDir)
262 best = prog;
263 }
264
265 return best;
266 }
267
268
269 /*
270 ===============
271 R_ParseProgramFile
272 ===============
273 */
R_ParseProgramFile(char * fixedName,qBool baseDir,qBool vp,qBool fp)274 static void R_ParseProgramFile (char *fixedName, qBool baseDir, qBool vp, qBool fp)
275 {
276 char *fileBuffer;
277 int fileLen;
278 char *vpBuf, *fpBuf;
279 char *start, *end;
280 char *token;
281 parse_t *ps;
282
283 // Load the file
284 Program_Printf (0, "...loading '%s'\n", fixedName);
285 fileLen = FS_LoadFile (fixedName, (void **)&fileBuffer, "\n\0");
286 if (!fileBuffer || fileLen <= 0) {
287 Program_DevPrintf (PRNT_ERROR, "...ERROR: couldn't load '%s' -- %s\n", fixedName, (fileLen == -1) ? "not found" : "empty");
288 return;
289 }
290
291 // Copy the buffer, and make certain it's newline and null terminated
292 if (vp) {
293 vpBuf = (char *)Mem_PoolAllocExt (fileLen, qFalse, ri.programSysPool, 0);
294 memcpy (vpBuf, fileBuffer, fileLen);
295 }
296 if (fp) {
297 fpBuf = (char *)Mem_PoolAllocExt (fileLen, qFalse, ri.programSysPool, 0);
298 memcpy (fpBuf, fileBuffer, fileLen);
299 }
300
301 // Don't need this anymore
302 FS_FreeFile (fileBuffer);
303
304 if (vp) {
305 fileBuffer = vpBuf;
306 ps = PS_StartSession (vpBuf, PSP_COMMENT_BLOCK|PSP_COMMENT_LINE|PSP_COMMENT_POUND);
307
308 start = end = NULL;
309
310 // Parse
311 for ( ; ; ) {
312 if (!PS_ParseToken (ps, PSF_ALLOW_NEWLINES, &token)) {
313 Program_Printf (PRNT_ERROR, "Missing '!!ARBvp1.0' header\n");
314 break;
315 }
316
317 // Header
318 if (!Q_stricmp (token, "!!ARBvp1.0")) {
319 start = ps->dataPtr - 10;
320
321 // Find the footer
322 for ( ; ; ) {
323 if (!PS_ParseToken (ps, PSF_ALLOW_NEWLINES, &token)) {
324 Program_Printf (PRNT_ERROR, "Missing 'END' footer!\n");
325 break;
326 }
327
328 if (!Q_stricmp (token, "END")) {
329 end = ps->dataPtr+4;
330 break;
331 }
332 }
333
334 if (end)
335 break;
336 }
337 }
338
339 if (start && end)
340 R_LoadProgram (fixedName, baseDir, GL_VERTEX_PROGRAM_ARB, start, end-start);
341
342 // Done
343 PS_EndSession (ps);
344 Mem_Free (fileBuffer);
345 }
346 if (fp) {
347 fileBuffer = fpBuf;
348 ps = PS_StartSession (fpBuf, PSP_COMMENT_BLOCK|PSP_COMMENT_LINE|PSP_COMMENT_POUND);
349
350 start = end = NULL;
351
352 // Parse
353 for ( ; ; ) {
354 if (!PS_ParseToken (ps, PSF_ALLOW_NEWLINES, &token)) {
355 Program_Printf (PRNT_ERROR, "Missing '!!ARBfp1.0' header\n");
356 break;
357 }
358
359 // Header
360 if (!Q_stricmp (token, "!!ARBfp1.0")) {
361 start = ps->dataPtr - 10;
362
363 // Find the footer
364 for ( ; ; ) {
365 if (!PS_ParseToken (ps, PSF_ALLOW_NEWLINES, &token)) {
366 Program_Printf (PRNT_ERROR, "Missing 'END' footer!\n");
367 break;
368 }
369
370 if (!Q_stricmp (token, "END")) {
371 end = ps->dataPtr+4;
372 break;
373 }
374 }
375
376 if (end)
377 break;
378 }
379 }
380
381 if (start && end)
382 R_LoadProgram (fixedName, baseDir, GL_FRAGMENT_PROGRAM_ARB, start, end-start);
383
384 // Done
385 PS_EndSession (ps);
386 Mem_Free (fileBuffer);
387 }
388 }
389
390 /*
391 ==============================================================================
392
393 CONSOLE FUNCTIONS
394
395 ==============================================================================
396 */
397
398 /*
399 ===============
400 R_ProgramList_f
401 ===============
402 */
R_ProgramList_f(void)403 static void R_ProgramList_f (void)
404 {
405 program_t *prog;
406 uint32 i;
407
408 Com_Printf (0, "------------------------------------------------------\n");
409 for (i=0, prog=r_programList ; i<r_numPrograms ; i++, prog++) {
410 Com_Printf (0, "%3i ", prog->progNum);
411 Com_Printf (0, "%5i ", prog->upInstructions);
412 Com_Printf (0, "%s ", prog->upNative ? "n" : "-");
413
414 switch (prog->target) {
415 case GL_FRAGMENT_PROGRAM_ARB: Com_Printf (0, "FP "); break;
416 case GL_VERTEX_PROGRAM_ARB: Com_Printf (0, "VP "); break;
417 }
418
419 Com_Printf (0, "%s\n", prog->name);
420 }
421 Com_Printf (0, "Total programs: %i\n", r_numPrograms);
422 Com_Printf (0, "------------------------------------------------------\n");
423 }
424
425 /*
426 ==============================================================================
427
428 INIT / SHUTDOWN
429
430 ==============================================================================
431 */
432
433 static void *cmd_programList;
434
435 /*
436 ===============
437 R_ProgramInit
438 ===============
439 */
R_ProgramInit(void)440 void R_ProgramInit (void)
441 {
442 char fixedName[MAX_QPATH];
443 char *fileList[MAX_PROGRAMS];
444 int numFiles, i;
445 qBool baseDir;
446 char *name;
447 uint32 initTime;
448
449 initTime = Sys_UMilliseconds ();
450 Com_Printf (0, "\n-------- Program Initialization --------\n");
451
452 // Commands
453 cmd_programList = Cmd_AddCommand ("programlist", R_ProgramList_f, "Prints out a list of currently loaded vertex and fragment programs");
454
455 r_numProgramErrors = 0;
456 r_numProgramWarnings = 0;
457
458 // Load *.vfp programs
459 Com_Printf (0, "Looking for *.vfp programs...\n");
460 numFiles = FS_FindFiles ("programs", "*programs/*.vfp", "vfp", fileList, MAX_PROGRAMS, qTrue, qTrue);
461 for (i=0 ; i<numFiles ; i++) {
462 // Fix the path
463 Com_NormalizePath (fixedName, sizeof (fixedName), fileList[i]);
464
465 // Check the path
466 name = strstr (fixedName, "/programs/");
467 if (!name)
468 continue; // This shouldn't happen...
469 name++; // Skip the initial '/'
470
471 // Base dir program?
472 baseDir = (strstr (fileList[i], BASE_MODDIRNAME "/")) ? qTrue : qFalse;
473
474 R_ParseProgramFile (name, baseDir, qTrue, qTrue);
475 }
476 FS_FreeFileList (fileList, numFiles);
477
478 // Load *.vp programs
479 Com_Printf (0, "Looking for *.vp programs...\n");
480 numFiles = FS_FindFiles ("programs", "*programs/*.vp", "vp", fileList, MAX_PROGRAMS, qTrue, qTrue);
481 for (i=0 ; i<numFiles ; i++) {
482 // Fix the path
483 Com_NormalizePath (fixedName, sizeof (fixedName), fileList[i]);
484
485 // Check the path
486 name = strstr (fixedName, "/programs/");
487 if (!name)
488 continue; // This shouldn't happen...
489 name++; // Skip the initial '/'
490
491 // Base dir program?
492 baseDir = (strstr (fileList[i], BASE_MODDIRNAME "/")) ? qTrue : qFalse;
493
494 R_ParseProgramFile (name, baseDir, qTrue, qFalse);
495 }
496 FS_FreeFileList (fileList, numFiles);
497
498 // Load *.fp programs
499 Com_Printf (0, "Looking for *.fp programs...\n");
500 numFiles = FS_FindFiles ("programs", "*programs/*.fp", "fp", fileList, MAX_PROGRAMS, qTrue, qTrue);
501 for (i=0 ; i<numFiles ; i++) {
502 // Fix the path
503 Com_NormalizePath (fixedName, sizeof (fixedName), fileList[i]);
504
505 // Check the path
506 name = strstr (fixedName, "/programs/");
507 if (!name)
508 continue; // This shouldn't happen...
509 name++; // Skip the initial '/'
510
511 // Base dir program?
512 baseDir = (strstr (fileList[i], BASE_MODDIRNAME "/")) ? qTrue : qFalse;
513
514 R_ParseProgramFile (name, baseDir, qFalse, qTrue);
515 }
516 FS_FreeFileList (fileList, numFiles);
517
518 Com_Printf (0, "----------------------------------------\n");
519
520 // Check for gl errors
521 GL_CheckForError ("R_ProgramInit");
522
523 // Check memory integrity
524 Mem_CheckPoolIntegrity (ri.programSysPool);
525
526 Com_Printf (0, "PROGRAMS - %i error(s), %i warning(s)\n", r_numProgramErrors, r_numProgramWarnings);
527 Com_Printf (0, "%u programs loaded in %ums\n", r_numPrograms, Sys_UMilliseconds()-initTime);
528 Com_Printf (0, "----------------------------------------\n");
529 }
530
531
532 /*
533 ===============
534 R_ProgramShutdown
535 ===============
536 */
R_ProgramShutdown(void)537 void R_ProgramShutdown (void)
538 {
539 program_t *prog;
540 uint32 size, i;
541
542 Com_Printf (0, "Program system shutdown:\n");
543
544 // Remove commands
545 Cmd_RemoveCommand ("programlist", cmd_programList);
546
547 // Shut the programs down
548 if (ri.config.extVertexProgram)
549 qglBindProgramARB (GL_VERTEX_PROGRAM_ARB, 0);
550 if (ri.config.extFragmentProgram)
551 qglBindProgramARB (GL_FRAGMENT_PROGRAM_ARB, 0);
552
553 for (i=0, prog=r_programList ; i<r_numPrograms ; i++, prog++) {
554 if (!prog->progNum)
555 continue; // Free r_programList slot
556
557 // Free it
558 qglDeleteProgramsARB (1, &prog->progNum);
559 }
560
561 r_numPrograms = 0;
562 memset (r_programList, 0, sizeof (program_t) * MAX_PROGRAMS);
563 memset (r_programHashTree, 0, sizeof (program_t *) * MAX_PROGRAMS);
564
565 size = Mem_FreePool (ri.programSysPool);
566 Com_Printf (0, "...releasing %u bytes...\n", size);
567 }
568