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