1 //----------------------------------------------------------------------------
2 //  EDGE OpenGL Rendering (Unit batching)
3 //----------------------------------------------------------------------------
4 //
5 //  Copyright (c) 1999-2009  The EDGE Team.
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //----------------------------------------------------------------------------
18 //
19 // -AJA- 2000/10/09: Began work on this new unit system.
20 //
21 
22 #include "i_defs.h"
23 #include "i_defs_gl.h"
24 
25 #include <vector>
26 #include <algorithm>
27 
28 #include "epi/image_data.h"
29 
30 #include "m_argv.h"
31 #include "r_gldefs.h"
32 #include "r_units.h"
33 #include "z_zone.h"
34 
35 #include "r_misc.h"
36 #include "r_image.h"
37 #include "r_texgl.h"
38 #include "r_shader.h"
39 
40 
41 cvar_c r_colorlighting;
42 cvar_c r_colormaterial;
43 
44 cvar_c r_dumbsky;
45 cvar_c r_dumbmulti;
46 cvar_c r_dumbcombine;
47 cvar_c r_dumbclamp;
48 
49 
50 #define MAX_L_VERT  4096
51 #define MAX_L_UNIT  (MAX_L_VERT / 4)
52 
53 #define DUMMY_CLAMP  789
54 
55 
56 // a single unit (polygon, quad, etc) to pass to the GL
57 typedef struct local_gl_unit_s
58 {
59 	// unit mode (e.g. GL_POLYGON)
60 	GLuint shape;
61 
62 	// environment modes (GL_REPLACE, GL_MODULATE, GL_DECAL, GL_ADD)
63 	GLuint env[2];
64 
65 	// texture(s) used
66 	GLuint tex[2];
67 
68 	// pass number (multiple pass rendering)
69 	int pass;
70 
71 	// blending flags
72 	int blending;
73 
74 	// range of local vertices
75 	int first, count;
76 }
77 local_gl_unit_t;
78 
79 
80 static local_gl_vert_t local_verts[MAX_L_VERT];
81 static local_gl_unit_t local_units[MAX_L_UNIT];
82 
83 static std::vector<local_gl_unit_t *> local_unit_map;
84 
85 static int cur_vert;
86 static int cur_unit;
87 
88 static bool batch_sort;
89 
90 
91 //
92 // RGL_InitUnits
93 //
94 // Initialise the unit system.  Once-only call.
95 //
RGL_InitUnits(void)96 void RGL_InitUnits(void)
97 {
98 	// Run the soft init code
99 	RGL_SoftInitUnits();
100 }
101 
102 //
103 // RGL_SoftInitUnits
104 //
105 // -ACB- 2004/02/15 Quickly-hacked routine to reinit stuff lost on res change
106 //
RGL_SoftInitUnits()107 void RGL_SoftInitUnits()
108 {
109 }
110 
111 
112 //
113 // RGL_StartUnits
114 //
115 // Starts a fresh batch of units.
116 //
117 // When 'sort_em' is true, the units will be sorted to keep
118 // texture changes to a minimum.  Otherwise, the batch is
119 // drawn in the same order as given.
120 //
RGL_StartUnits(bool sort_em)121 void RGL_StartUnits(bool sort_em)
122 {
123 	cur_vert = cur_unit = 0;
124 
125 	batch_sort = sort_em;
126 
127 	local_unit_map.resize(MAX_L_UNIT);
128 }
129 
130 //
131 // RGL_FinishUnits
132 //
133 // Finishes a batch of units, drawing any that haven't been drawn yet.
134 //
RGL_FinishUnits(void)135 void RGL_FinishUnits(void)
136 {
137 	RGL_DrawUnits();
138 }
139 
140 
myActiveTexture(GLuint id)141 static inline void myActiveTexture(GLuint id)
142 {
143 	if (GLEW_VERSION_1_3)
144 		glActiveTexture(id);
145 	else /* GLEW_ARB_multitexture */
146 		glActiveTextureARB(id);
147 }
148 
myMultiTexCoord2f(GLuint id,GLfloat s,GLfloat t)149 static inline void myMultiTexCoord2f(GLuint id, GLfloat s, GLfloat t)
150 {
151 	if (GLEW_VERSION_1_3)
152 		glMultiTexCoord2f(id, s, t);
153 	else /* GLEW_ARB_multitexture */
154 		glMultiTexCoord2fARB(id, s, t);
155 }
156 
157 //
158 // RGL_BeginUnit
159 //
160 // Begin a new unit, with the given parameters (mode and texture ID).
161 // `max_vert' is the maximum expected vertices of the quad/poly (the
162 // actual number can be less, but never more).  Returns a pointer to
163 // the first vertex structure.  `masked' should be true if the texture
164 // contains "holes" (like sprites).  `blended' should be true if the
165 // texture should be blended (like for translucent water or sprites).
166 //
RGL_BeginUnit(GLuint shape,int max_vert,GLuint env1,GLuint tex1,GLuint env2,GLuint tex2,int pass,int blending)167 local_gl_vert_t *RGL_BeginUnit(GLuint shape, int max_vert,
168 		                       GLuint env1, GLuint tex1,
169 							   GLuint env2, GLuint tex2,
170 							   int pass, int blending)
171 {
172 	local_gl_unit_t *unit;
173 
174 	SYS_ASSERT(max_vert > 0);
175 	SYS_ASSERT(pass >= 0);
176 
177 	SYS_ASSERT((blending & BL_CULL_BOTH) != BL_CULL_BOTH);
178 
179 	// check we have enough space left
180 	if (cur_vert + max_vert > MAX_L_VERT || cur_unit >= MAX_L_UNIT)
181 	{
182 		RGL_DrawUnits();
183 	}
184 
185 	unit = local_units + cur_unit;
186 
187 	if (env1 == ENV_NONE) tex1 = 0;
188 	if (env2 == ENV_NONE) tex2 = 0;
189 
190 	unit->shape  = shape;
191 	unit->env[0] = env1;
192 	unit->env[1] = env2;
193 	unit->tex[0] = tex1;
194 	unit->tex[1] = tex2;
195 
196 	unit->pass     = pass;
197 	unit->blending = blending;
198 	unit->first    = cur_vert;  // count set later
199 
200 	return local_verts + cur_vert;
201 }
202 
203 //
204 // RGL_EndUnit
205 //
RGL_EndUnit(int actual_vert)206 void RGL_EndUnit(int actual_vert)
207 {
208 	local_gl_unit_t *unit;
209 
210 	SYS_ASSERT(actual_vert > 0);
211 
212 	unit = local_units + cur_unit;
213 
214 	unit->count = actual_vert;
215 
216 	// adjust colors (for special effects)
217 	for (int i = 0; i < actual_vert; i++)
218 	{
219 		local_gl_vert_t *v = &local_verts[cur_vert + i];
220 
221 		v->rgba[0] *= ren_red_mul;
222 		v->rgba[1] *= ren_grn_mul;
223 		v->rgba[2] *= ren_blu_mul;
224 	}
225 
226 	cur_vert += actual_vert;
227 	cur_unit++;
228 
229 	SYS_ASSERT(cur_vert <= MAX_L_VERT);
230 	SYS_ASSERT(cur_unit <= MAX_L_UNIT);
231 }
232 
233 
234 struct Compare_Unit_pred
235 {
operator ()Compare_Unit_pred236 	inline bool operator() (const local_gl_unit_t *A, const local_gl_unit_t *B) const
237 	{
238 		if (A->pass != B->pass)
239 			return A->pass < B->pass;
240 
241 		if (A->tex[0] != B->tex[0])
242 			return A->tex[0] < B->tex[0];
243 
244 		if (A->tex[1] != B->tex[1])
245 			return A->tex[1] < B->tex[1];
246 
247 		if (A->env[0] != B->env[0])
248 			return A->env[0] < B->env[0];
249 
250 		if (A->env[1] != B->env[1])
251 			return A->env[1] < B->env[1];
252 
253 		return A->blending < B->blending;
254 	}
255 };
256 
EnableCustomEnv(GLuint env,bool enable)257 static void EnableCustomEnv(GLuint env, bool enable)
258 {
259 	switch (env)
260 	{
261 		case ENV_SKIP_RGB:
262 			if (enable)
263 			{
264 				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
265 				glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
266 				glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
267 			}
268 			else
269 			{
270 				/* no need to modify TEXTURE_ENV_MODE */
271 				glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
272 				glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
273 			}
274 			break;
275 
276 		default:
277 			I_Error("INTERNAL ERROR: no such custom env: %08x\n", env);
278 	}
279 }
280 
RGL_SendRawVector(const local_gl_vert_t * V)281 static inline void RGL_SendRawVector(const local_gl_vert_t *V)
282 {
283 	if (r_colormaterial.d || ! r_colorlighting.d)
284 		glColor4fv(V->rgba);
285 	else
286 	{
287 		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, V->rgba);
288 		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, V->rgba);
289 	}
290 
291 	myMultiTexCoord2f(GL_TEXTURE0, V->texc[0].x, V->texc[0].y);
292 	myMultiTexCoord2f(GL_TEXTURE1, V->texc[1].x, V->texc[1].y);
293 
294 	glNormal3f(V->normal.x, V->normal.y, V->normal.z);
295 	glEdgeFlag(V->edge);
296 
297 	// vertex must be last
298 	glVertex3f(V->pos.x, V->pos.y, V->pos.z);
299 }
300 
301 //
302 // RGL_DrawUnits
303 //
304 // Forces the set of current units to be drawn.  This call is
305 // optional (it never _needs_ to be called by client code).
306 //
RGL_DrawUnits(void)307 void RGL_DrawUnits(void)
308 {
309 	if (cur_unit == 0)
310 		return;
311 
312 	GLuint active_tex[2] = { 0, 0 };
313 	GLuint active_env[2] = { 0, 0 };
314 
315 	int active_pass = 0;
316 	int active_blending = 0;
317 
318 	for (int i=0; i < cur_unit; i++)
319 		local_unit_map[i] = & local_units[i];
320 
321 	if (batch_sort)
322 	{
323 		std::sort(local_unit_map.begin(),
324 				  local_unit_map.begin() + cur_unit,
325 				  Compare_Unit_pred());
326 	}
327 
328 	glDisable(GL_TEXTURE_2D);
329 	glDisable(GL_ALPHA_TEST);
330 	glDisable(GL_BLEND);
331 
332 	glAlphaFunc(GL_GREATER, 0);
333 
334 	glPolygonOffset(0, 0);
335 
336 
337 	for (int j=0; j < cur_unit; j++)
338 	{
339 		local_gl_unit_t *unit = local_unit_map[j];
340 
341 		SYS_ASSERT(unit->count > 0);
342 
343 		// detect changes in texture/alpha/blending state
344 
345 		if (active_pass != unit->pass)
346 		{
347 			active_pass = unit->pass;
348 
349 			glPolygonOffset(0, -active_pass);
350 		}
351 
352 		if ((active_blending ^ unit->blending) & (BL_Masked | BL_Less))
353 		{
354 			if (unit->blending & BL_Less)
355 			{
356 				// glAlphaFunc is updated below, because the alpha
357 				// value can change from unit to unit while the
358 				// BL_Less flag remains set.
359 				glEnable(GL_ALPHA_TEST);
360 			}
361 			else if (unit->blending & BL_Masked)
362 			{
363 				glEnable(GL_ALPHA_TEST);
364 				glAlphaFunc(GL_GREATER, 0);
365 			}
366 			else
367 				glDisable(GL_ALPHA_TEST);
368 		}
369 
370 		if ((active_blending ^ unit->blending) & (BL_Alpha | BL_Add))
371 		{
372 			if (unit->blending & BL_Add)
373 			{
374 				glEnable(GL_BLEND);
375 				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
376 			}
377 			else if (unit->blending & BL_Alpha)
378 			{
379 				glEnable(GL_BLEND);
380 				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
381 			}
382 			else
383 				glDisable(GL_BLEND);
384 		}
385 
386 		if ((active_blending ^ unit->blending) & BL_CULL_BOTH)
387 		{
388 			if (unit->blending & BL_CULL_BOTH)
389 			{
390 				glEnable(GL_CULL_FACE);
391 				glCullFace((unit->blending & BL_CullFront) ? GL_FRONT : GL_BACK);
392 			}
393 			else
394 				glDisable(GL_CULL_FACE);
395 		}
396 
397 		if ((active_blending ^ unit->blending) & BL_NoZBuf)
398 		{
399 			glDepthMask((unit->blending & BL_NoZBuf) ? GL_FALSE : GL_TRUE);
400 		}
401 
402 		active_blending = unit->blending;
403 
404 		if (active_blending & BL_Less)
405 		{
406 			// NOTE: assumes alpha is constant over whole polygon
407 			float a = local_verts[unit->first].rgba[3];
408 
409 			glAlphaFunc(GL_GREATER, a * 0.66f);
410 		}
411 
412 		for (int t=1; t >= 0; t--)
413 		{
414 			myActiveTexture(GL_TEXTURE0 + t);
415 
416 			if (active_tex[t] != unit->tex[t])
417 			{
418 				if (unit->tex[t] == 0)
419 					glDisable(GL_TEXTURE_2D);
420 				else if (active_tex[t] == 0)
421 					glEnable(GL_TEXTURE_2D);
422 
423 				if (unit->tex[t] != 0)
424 					glBindTexture(GL_TEXTURE_2D, unit->tex[t]);
425 
426 				active_tex[t] = unit->tex[t];
427 			}
428 
429 			if (active_env[t] != unit->env[t])
430 			{
431 				if (active_env[t] >= CUSTOM_ENV_BEGIN &&
432 					active_env[t] <= CUSTOM_ENV_END)
433 				{
434 					EnableCustomEnv(active_env[t], false);
435 				}
436 
437 				if (unit->env[t] >= CUSTOM_ENV_BEGIN &&
438 					unit->env[t] <= CUSTOM_ENV_END)
439 				{
440 					EnableCustomEnv(unit->env[t], true);
441 				}
442 				else if (unit->env[t] != ENV_NONE)
443 					glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->env[t]);
444 
445 				active_env[t] = unit->env[t];
446 			}
447 		}
448 
449 		GLint old_clamp = DUMMY_CLAMP;
450 
451 		if ((active_blending & BL_ClampY) && active_tex[0] != 0)
452 		{
453 			glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &old_clamp);
454 
455 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
456 				r_dumbclamp.d ? GL_CLAMP : GL_CLAMP_TO_EDGE);
457 		}
458 
459 		glBegin(unit->shape);
460 
461 		for (int v_idx=0; v_idx < unit->count; v_idx++)
462 		{
463 			RGL_SendRawVector(local_verts + unit->first + v_idx);
464 		}
465 
466 		glEnd();
467 
468 		// restore the clamping mode
469 		if (old_clamp != DUMMY_CLAMP)
470 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, old_clamp);
471 	}
472 
473 	// all done
474 	cur_vert = cur_unit = 0;
475 
476 	glPolygonOffset(0, 0);
477 
478 	for (int t=1; t >=0; t--)
479 	{
480 		myActiveTexture(GL_TEXTURE0 + t);
481 
482 		if (active_env[t] >= CUSTOM_ENV_BEGIN &&
483 			active_env[t] <= CUSTOM_ENV_END)
484 		{
485 			EnableCustomEnv(active_env[t], false);
486 		}
487 		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
488 		glDisable(GL_TEXTURE_2D);
489 	}
490 
491 	glDepthMask(GL_TRUE);
492 	glCullFace(GL_BACK);
493 
494 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
495 	glAlphaFunc(GL_GREATER, 0);
496 
497 	glDisable(GL_ALPHA_TEST);
498 	glDisable(GL_BLEND);
499 	glDisable(GL_CULL_FACE);
500 }
501 
502 
503 //--- editor settings ---
504 // vi:ts=4:sw=4:noexpandtab
505