1 /* Copyright (C) 2013 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. 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. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /*
19 * Terrain rendering (everything related to patches and water) is
20 * encapsulated in TerrainRenderer
21 */
22
23 #include "precompiled.h"
24
25 #include "graphics/Camera.h"
26 #include "graphics/Decal.h"
27 #include "graphics/LightEnv.h"
28 #include "graphics/LOSTexture.h"
29 #include "graphics/Patch.h"
30 #include "graphics/GameView.h"
31 #include "graphics/Model.h"
32 #include "graphics/ShaderManager.h"
33 #include "renderer/ShadowMap.h"
34 #include "renderer/SkyManager.h"
35 #include "graphics/TerritoryTexture.h"
36 #include "graphics/TextRenderer.h"
37
38 #include "maths/MathUtil.h"
39
40 #include "ps/Filesystem.h"
41 #include "ps/CLogger.h"
42 #include "ps/Game.h"
43 #include "ps/Profile.h"
44 #include "ps/World.h"
45
46 #include "renderer/DecalRData.h"
47 #include "renderer/PatchRData.h"
48 #include "renderer/Renderer.h"
49 #include "renderer/ShadowMap.h"
50 #include "renderer/TerrainRenderer.h"
51 #include "renderer/VertexArray.h"
52 #include "renderer/WaterManager.h"
53
54 #include "tools/atlas/GameInterface/GameLoop.h"
55
56 extern GameLoopState* g_AtlasGameLoop;
57
58 ///////////////////////////////////////////////////////////////////////////////////////////////
59 // TerrainRenderer implementation
60
61
62 /**
63 * TerrainRenderer keeps track of which phase it is in, to detect
64 * when Submit, PrepareForRendering etc. are called in the wrong order.
65 */
66 enum Phase {
67 Phase_Submit,
68 Phase_Render
69 };
70
71
72 /**
73 * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class.
74 */
75 struct TerrainRendererInternals
76 {
77 /// Which phase (submitting or rendering patches) are we in right now?
78 Phase phase;
79
80 /// Patches that were submitted for this frame
81 std::vector<CPatchRData*> visiblePatches[CRenderer::CULL_MAX];
82
83 /// Decals that were submitted for this frame
84 std::vector<CDecalRData*> visibleDecals[CRenderer::CULL_MAX];
85
86 /// Fancy water shader
87 CShaderProgramPtr fancyWaterShader;
88
89 CSimulation2* simulation;
90 };
91
92
93
94 ///////////////////////////////////////////////////////////////////
95 // Construction/Destruction
TerrainRenderer()96 TerrainRenderer::TerrainRenderer()
97 {
98 m = new TerrainRendererInternals();
99 m->phase = Phase_Submit;
100 }
101
~TerrainRenderer()102 TerrainRenderer::~TerrainRenderer()
103 {
104 delete m;
105 }
106
SetSimulation(CSimulation2 * simulation)107 void TerrainRenderer::SetSimulation(CSimulation2* simulation)
108 {
109 m->simulation = simulation;
110 }
111
112 ///////////////////////////////////////////////////////////////////
113 // Submit a patch for rendering
Submit(int cullGroup,CPatch * patch)114 void TerrainRenderer::Submit(int cullGroup, CPatch* patch)
115 {
116 ENSURE(m->phase == Phase_Submit);
117
118 CPatchRData* data = (CPatchRData*)patch->GetRenderData();
119 if (data == 0)
120 {
121 // no renderdata for patch, create it now
122 data = new CPatchRData(patch, m->simulation);
123 patch->SetRenderData(data);
124 }
125 data->Update(m->simulation);
126
127 m->visiblePatches[cullGroup].push_back(data);
128 }
129
130 ///////////////////////////////////////////////////////////////////
131 // Submit a decal for rendering
Submit(int cullGroup,CModelDecal * decal)132 void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal)
133 {
134 ENSURE(m->phase == Phase_Submit);
135
136 CDecalRData* data = (CDecalRData*)decal->GetRenderData();
137 if (data == 0)
138 {
139 // no renderdata for decal, create it now
140 data = new CDecalRData(decal, m->simulation);
141 decal->SetRenderData(data);
142 }
143 data->Update(m->simulation);
144
145 m->visibleDecals[cullGroup].push_back(data);
146 }
147
148 ///////////////////////////////////////////////////////////////////
149 // Prepare for rendering
PrepareForRendering()150 void TerrainRenderer::PrepareForRendering()
151 {
152 ENSURE(m->phase == Phase_Submit);
153
154 m->phase = Phase_Render;
155 }
156
157 ///////////////////////////////////////////////////////////////////
158 // Clear submissions lists
EndFrame()159 void TerrainRenderer::EndFrame()
160 {
161 ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit);
162
163 for (int i = 0; i < CRenderer::CULL_MAX; ++i)
164 {
165 m->visiblePatches[i].clear();
166 m->visibleDecals[i].clear();
167 }
168
169 m->phase = Phase_Submit;
170 }
171
172
173 ///////////////////////////////////////////////////////////////////
174 // Full-featured terrain rendering with blending and everything
RenderTerrain(int cullGroup)175 void TerrainRenderer::RenderTerrain(int cullGroup)
176 {
177 #if CONFIG2_GLES
178 UNUSED2(cullGroup);
179 #else
180 ENSURE(m->phase == Phase_Render);
181
182 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
183 std::vector<CDecalRData*>& visibleDecals = m->visibleDecals[cullGroup];
184 if (visiblePatches.empty() && visibleDecals.empty())
185 return;
186
187 CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("fixed:dummy", CShaderDefines());
188 dummyShader->Bind();
189
190 // render the solid black sides of the map first
191 g_Renderer.BindTexture(0, 0);
192 glEnableClientState(GL_VERTEX_ARRAY);
193 glColor3f(0, 0, 0);
194 PROFILE_START("render terrain sides");
195 for (size_t i = 0; i < visiblePatches.size(); ++i)
196 visiblePatches[i]->RenderSides(dummyShader);
197 PROFILE_END("render terrain sides");
198
199 // switch on required client states
200 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
201
202 // render everything fullbright
203 // set up texture environment for base pass
204 pglActiveTextureARB(GL_TEXTURE0);
205 pglClientActiveTextureARB(GL_TEXTURE0);
206 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
207 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
208 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
209 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
210
211 // Set alpha to 1.0
212 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
213 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT);
214 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
215 static const float one[4] = { 1.f, 1.f, 1.f, 1.f };
216 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, one);
217
218 PROFILE_START("render terrain base");
219 CPatchRData::RenderBases(visiblePatches, CShaderDefines(), NULL, true, dummyShader);
220 PROFILE_END("render terrain base");
221
222 // render blends
223 // switch on the composite alpha map texture
224 (void)ogl_tex_bind(g_Renderer.m_hCompositeAlphaMap, 1);
225
226 // switch on second uv set
227 pglClientActiveTextureARB(GL_TEXTURE1);
228 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
229
230 // setup additional texenv required by blend pass
231 pglActiveTextureARB(GL_TEXTURE1);
232 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
233 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
234 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
235 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
236 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
237 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
238 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA);
239
240 // switch on blending
241 glEnable(GL_BLEND);
242 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
243
244 // no need to write to the depth buffer a second time
245 glDepthMask(0);
246
247 // The decal color array contains lighting data, which we don't want in this non-shader mode
248 glDisableClientState(GL_COLOR_ARRAY);
249
250 // render blend passes for each patch
251 PROFILE_START("render terrain blends");
252 CPatchRData::RenderBlends(visiblePatches, CShaderDefines(), NULL, true, dummyShader);
253 PROFILE_END("render terrain blends");
254
255 // Disable second texcoord array
256 pglClientActiveTextureARB(GL_TEXTURE1);
257 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
258
259
260 // Render terrain decals
261
262 g_Renderer.BindTexture(1, 0);
263 pglActiveTextureARB(GL_TEXTURE0);
264 pglClientActiveTextureARB(GL_TEXTURE0);
265 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
266 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
267 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
268 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
269 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
270 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
271 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
272 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
273 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
274
275 PROFILE_START("render terrain decals");
276 CDecalRData::RenderDecals(visibleDecals, CShaderDefines(), NULL, true, dummyShader);
277 PROFILE_END("render terrain decals");
278
279
280 // Now apply lighting
281 const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
282
283 pglClientActiveTextureARB(GL_TEXTURE0);
284 glEnableClientState(GL_COLOR_ARRAY); // diffuse lighting colors
285
286 // The vertex color is scaled by 0.5 to permit overbrightness without clamping.
287 // We therefore need to draw clamp((texture*lighting)*2.0), where 'texture'
288 // is what previous passes drew onto the framebuffer, and 'lighting' is the
289 // color computed by this pass.
290 // We can do that with blending by getting it to draw dst*src + src*dst:
291 glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
292
293 // Scale the ambient color by 0.5 to match the vertex diffuse colors
294 float terrainAmbientColor[4] = {
295 lightEnv.m_TerrainAmbientColor.X * 0.5f,
296 lightEnv.m_TerrainAmbientColor.Y * 0.5f,
297 lightEnv.m_TerrainAmbientColor.Z * 0.5f,
298 1.f
299 };
300
301 CLOSTexture& losTexture = g_Renderer.GetScene().GetLOSTexture();
302
303 int streamflags = STREAM_POS|STREAM_COLOR;
304
305 pglActiveTextureARB(GL_TEXTURE0);
306 // We're not going to use a texture here, but we have to have a valid texture
307 // bound else the texture unit will be disabled.
308 // We should still have a bound splat texture from some earlier rendering,
309 // so assume that's still valid to use.
310 // (TODO: That's a bit of an ugly hack.)
311
312 // No shadows: (Ambient + Diffuse) * LOS
313 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
314 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD);
315 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
316 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
317 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT);
318 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
319 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
320 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
321 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
322
323 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, terrainAmbientColor);
324
325 losTexture.BindTexture(1);
326 pglClientActiveTextureARB(GL_TEXTURE1);
327 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
328 streamflags |= STREAM_POSTOUV1;
329
330 glMatrixMode(GL_TEXTURE);
331 glLoadMatrixf(&losTexture.GetTextureMatrix()._11);
332 glMatrixMode(GL_MODELVIEW);
333
334 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
335 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
336 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
337 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
338 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
339 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
340 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
341 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
342 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
343
344 pglActiveTextureARB(GL_TEXTURE0);
345 pglClientActiveTextureARB(GL_TEXTURE0);
346
347 PROFILE_START("render terrain streams");
348 CPatchRData::RenderStreams(visiblePatches, dummyShader, streamflags);
349 PROFILE_END("render terrain streams");
350
351 glMatrixMode(GL_TEXTURE);
352 glLoadIdentity();
353 glMatrixMode(GL_MODELVIEW);
354
355 // restore OpenGL state
356 g_Renderer.BindTexture(1, 0);
357
358 pglClientActiveTextureARB(GL_TEXTURE1);
359 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
360 glMatrixMode(GL_TEXTURE);
361 glLoadIdentity();
362 glMatrixMode(GL_MODELVIEW);
363
364 pglClientActiveTextureARB(GL_TEXTURE0);
365 pglActiveTextureARB(GL_TEXTURE0);
366
367 glDepthMask(1);
368 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
369 glDisable(GL_BLEND);
370 glDisableClientState(GL_COLOR_ARRAY);
371 glDisableClientState(GL_VERTEX_ARRAY);
372 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
373
374 dummyShader->Unbind();
375 #endif
376 }
377
RenderTerrainOverlayTexture(int cullGroup,CMatrix3D & textureMatrix)378 void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix)
379 {
380 #if CONFIG2_GLES
381 #warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES
382 UNUSED2(cullGroup);
383 UNUSED2(textureMatrix);
384 #else
385 ENSURE(m->phase == Phase_Render);
386
387 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
388
389 glEnableClientState(GL_VERTEX_ARRAY);
390 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
391
392 pglActiveTextureARB(GL_TEXTURE0);
393 glEnable(GL_TEXTURE_2D);
394 glEnable(GL_BLEND);
395 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
396 glDepthMask(0);
397 glDisable(GL_DEPTH_TEST);
398
399 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
400
401 glMatrixMode(GL_TEXTURE);
402 glLoadMatrixf(&textureMatrix._11);
403 glMatrixMode(GL_MODELVIEW);
404
405 CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("fixed:dummy", CShaderDefines());
406 dummyShader->Bind();
407 CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS|STREAM_POSTOUV0);
408 dummyShader->Unbind();
409
410 // To make the overlay visible over water, render an additional map-sized
411 // water-height patch
412 CBoundingBoxAligned waterBounds;
413 for (size_t i = 0; i < visiblePatches.size(); ++i)
414 {
415 CPatchRData* data = visiblePatches[i];
416 waterBounds += data->GetWaterBounds();
417 }
418 if (!waterBounds.IsEmpty())
419 {
420 float h = g_Renderer.GetWaterManager()->m_WaterHeight + 0.05f; // add a delta to avoid z-fighting
421 float waterPos[] = {
422 waterBounds[0].X, h, waterBounds[0].Z,
423 waterBounds[1].X, h, waterBounds[0].Z,
424 waterBounds[0].X, h, waterBounds[1].Z,
425 waterBounds[1].X, h, waterBounds[1].Z
426 };
427 glVertexPointer(3, GL_FLOAT, 3*sizeof(float), waterPos);
428 glTexCoordPointer(3, GL_FLOAT, 3*sizeof(float), waterPos);
429 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
430 }
431
432 glMatrixMode(GL_TEXTURE);
433 glLoadIdentity();
434 glMatrixMode(GL_MODELVIEW);
435
436 glDepthMask(1);
437 glEnable(GL_DEPTH_TEST);
438 glDisable(GL_BLEND);
439 glDisableClientState(GL_COLOR_ARRAY);
440 glDisableClientState(GL_VERTEX_ARRAY);
441 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
442 #endif
443 }
444
445
446 ///////////////////////////////////////////////////////////////////
447
448 /**
449 * Set up all the uniforms for a shader pass.
450 */
PrepareShader(const CShaderProgramPtr & shader,ShadowMap * shadow)451 void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow)
452 {
453 shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
454 shader->Uniform(str_cameraPos, g_Renderer.GetViewCamera().GetOrientation().GetTranslation());
455
456 const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
457
458 if (shadow)
459 {
460 shader->BindTexture(str_shadowTex, shadow->GetTexture());
461 shader->Uniform(str_shadowTransform, shadow->GetTextureMatrix());
462 int width = shadow->GetWidth();
463 int height = shadow->GetHeight();
464 shader->Uniform(str_shadowScale, width, height, 1.0f / width, 1.0f / height);
465 }
466
467 CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
468 shader->BindTexture(str_losTex, los.GetTextureSmooth());
469 shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
470
471 shader->Uniform(str_ambient, lightEnv.m_TerrainAmbientColor);
472 shader->Uniform(str_sunColor, lightEnv.m_SunColor);
473 shader->Uniform(str_sunDir, lightEnv.GetSunDir());
474
475 shader->Uniform(str_fogColor, lightEnv.m_FogColor);
476 shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
477 }
478
RenderTerrainShader(const CShaderDefines & context,int cullGroup,ShadowMap * shadow)479 void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
480 {
481 ENSURE(m->phase == Phase_Render);
482
483 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
484 std::vector<CDecalRData*>& visibleDecals = m->visibleDecals[cullGroup];
485 if (visiblePatches.empty() && visibleDecals.empty())
486 return;
487
488 // render the solid black sides of the map first
489 CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
490 techSolid->BeginPass();
491 CShaderProgramPtr shaderSolid = techSolid->GetShader();
492 shaderSolid->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
493 shaderSolid->Uniform(str_color, 0.0f, 0.0f, 0.0f, 1.0f);
494
495 PROFILE_START("render terrain sides");
496 for (size_t i = 0; i < visiblePatches.size(); ++i)
497 visiblePatches[i]->RenderSides(shaderSolid);
498 PROFILE_END("render terrain sides");
499
500 techSolid->EndPass();
501
502 PROFILE_START("render terrain base");
503 CPatchRData::RenderBases(visiblePatches, context, shadow);
504 PROFILE_END("render terrain base");
505
506 // no need to write to the depth buffer a second time
507 glDepthMask(0);
508
509 // render blend passes for each patch
510 PROFILE_START("render terrain blends");
511 CPatchRData::RenderBlends(visiblePatches, context, shadow, false);
512 PROFILE_END("render terrain blends");
513
514 PROFILE_START("render terrain decals");
515 CDecalRData::RenderDecals(visibleDecals, context, shadow, false);
516 PROFILE_END("render terrain decals");
517
518 // restore OpenGL state
519 g_Renderer.BindTexture(1, 0);
520 g_Renderer.BindTexture(2, 0);
521 g_Renderer.BindTexture(3, 0);
522
523 glDepthMask(1);
524 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
525 glDisable(GL_BLEND);
526 }
527
528
529 ///////////////////////////////////////////////////////////////////
530 // Render un-textured patches as polygons
RenderPatches(int cullGroup)531 void TerrainRenderer::RenderPatches(int cullGroup)
532 {
533 ENSURE(m->phase == Phase_Render);
534
535 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
536 if (visiblePatches.empty())
537 return;
538
539 #if CONFIG2_GLES
540 #warning TODO: implement TerrainRenderer::RenderPatches for GLES
541 #else
542 CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("fixed:dummy", CShaderDefines());
543 dummyShader->Bind();
544
545 glEnableClientState(GL_VERTEX_ARRAY);
546 CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS);
547 glDisableClientState(GL_VERTEX_ARRAY);
548
549 dummyShader->Unbind();
550 #endif
551 }
552
553
554 ///////////////////////////////////////////////////////////////////
555 // Render outlines of submitted patches as lines
RenderOutlines(int cullGroup)556 void TerrainRenderer::RenderOutlines(int cullGroup)
557 {
558 ENSURE(m->phase == Phase_Render);
559
560 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
561 if (visiblePatches.empty())
562 return;
563
564 #if CONFIG2_GLES
565 #warning TODO: implement TerrainRenderer::RenderOutlines for GLES
566 #else
567 glEnableClientState(GL_VERTEX_ARRAY);
568 for (size_t i = 0; i < visiblePatches.size(); ++i)
569 visiblePatches[i]->RenderOutline();
570 glDisableClientState(GL_VERTEX_ARRAY);
571 #endif
572 }
573
574
575 ///////////////////////////////////////////////////////////////////
576 // Scissor rectangle of water patches
ScissorWater(int cullGroup,const CMatrix3D & viewproj)577 CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CMatrix3D &viewproj)
578 {
579 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
580
581 CBoundingBoxAligned scissor;
582 for (size_t i = 0; i < visiblePatches.size(); ++i)
583 {
584 CPatchRData* data = visiblePatches[i];
585 const CBoundingBoxAligned& waterBounds = data->GetWaterBounds();
586 if (waterBounds.IsEmpty())
587 continue;
588
589 CVector4D v1 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f));
590 CVector4D v2 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f));
591 CVector4D v3 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f));
592 CVector4D v4 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f));
593 CBoundingBoxAligned screenBounds;
594 #define ADDBOUND(v1, v2, v3, v4) \
595 if (v1.Z >= -v1.W) \
596 screenBounds += CVector3D(v1.X, v1.Y, v1.Z) * (1.0f / v1.W); \
597 else \
598 { \
599 float t = v1.Z + v1.W; \
600 if (v2.Z > -v2.W) \
601 { \
602 CVector4D c2 = v1 + (v2 - v1) * (t / (t - (v2.Z + v2.W))); \
603 screenBounds += CVector3D(c2.X, c2.Y, c2.Z) * (1.0f / c2.W); \
604 } \
605 if (v3.Z > -v3.W) \
606 { \
607 CVector4D c3 = v1 + (v3 - v1) * (t / (t - (v3.Z + v3.W))); \
608 screenBounds += CVector3D(c3.X, c3.Y, c3.Z) * (1.0f / c3.W); \
609 } \
610 if (v4.Z > -v4.W) \
611 { \
612 CVector4D c4 = v1 + (v4 - v1) * (t / (t - (v4.Z + v4.W))); \
613 screenBounds += CVector3D(c4.X, c4.Y, c4.Z) * (1.0f / c4.W); \
614 } \
615 }
616 ADDBOUND(v1, v2, v3, v4);
617 ADDBOUND(v2, v1, v3, v4);
618 ADDBOUND(v3, v1, v2, v4);
619 ADDBOUND(v4, v1, v2, v3);
620 #undef ADDBOUND
621 if (screenBounds[0].X >= 1.0f || screenBounds[1].X <= -1.0f || screenBounds[0].Y >= 1.0f || screenBounds[1].Y <= -1.0f)
622 continue;
623 scissor += screenBounds;
624 }
625 return CBoundingBoxAligned(CVector3D(clamp(scissor[0].X, -1.0f, 1.0f), clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f),
626 CVector3D(clamp(scissor[1].X, -1.0f, 1.0f), clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f));
627 }
628
629 // Render fancy water
RenderFancyWater(const CShaderDefines & context,int cullGroup,ShadowMap * shadow)630 bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
631 {
632 PROFILE3_GPU("fancy water");
633
634 WaterManager* WaterMgr = g_Renderer.GetWaterManager();
635 CShaderDefines defines = context;
636
637 // If we're using fancy water, make sure its shader is loaded
638 if (!m->fancyWaterShader || WaterMgr->m_NeedsReloading)
639 {
640 if (WaterMgr->m_WaterRealDepth)
641 defines.Add(str_USE_REAL_DEPTH, str_1);
642 if (WaterMgr->m_WaterFancyEffects)
643 defines.Add(str_USE_FANCY_EFFECTS, str_1);
644 if (WaterMgr->m_WaterRefraction)
645 defines.Add(str_USE_REFRACTION, str_1);
646 if (WaterMgr->m_WaterReflection)
647 defines.Add(str_USE_REFLECTION, str_1);
648 if (shadow && WaterMgr->m_WaterShadows)
649 defines.Add(str_USE_SHADOWS_ON_WATER, str_1);
650
651 // haven't updated the ARB shader yet so I'll always load the GLSL
652 /*if (!g_Renderer.m_Options.m_PreferGLSL && !superFancy)
653 m->fancyWaterShader = g_Renderer.GetShaderManager().LoadProgram("arb/water_high", defines);
654 else*/
655 m->fancyWaterShader = g_Renderer.GetShaderManager().LoadProgram("glsl/water_high", defines);
656
657 if (!m->fancyWaterShader)
658 {
659 LOGERROR("Failed to load water shader. Falling back to fixed pipeline water.\n");
660 WaterMgr->m_RenderWater = false;
661 return false;
662 }
663 WaterMgr->m_NeedsReloading = false;
664 }
665
666 CLOSTexture& losTexture = g_Renderer.GetScene().GetLOSTexture();
667
668 // creating the real depth texture using the depth buffer.
669 if (WaterMgr->m_WaterRealDepth)
670 {
671 if (WaterMgr->m_depthTT == 0)
672 {
673 GLuint depthTex;
674 glGenTextures(1, (GLuint*)&depthTex);
675 WaterMgr->m_depthTT = depthTex;
676 glBindTexture(GL_TEXTURE_2D, WaterMgr->m_depthTT);
677 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, g_Renderer.GetWidth(), g_Renderer.GetHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE,NULL);
678 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
679 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
680 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
681 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
682 }
683 else
684 {
685 glBindTexture(GL_TEXTURE_2D, WaterMgr->m_depthTT);
686 glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight(), 0);
687 }
688 glBindTexture(GL_TEXTURE_2D, 0);
689 }
690 // Calculating the advanced informations about Foam and all if the quality calls for it.
691 /*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves))
692 {
693 WaterMgr->m_NeedInfoUpdate = false;
694 WaterMgr->CreateSuperfancyInfo();
695 }*/
696
697 double time = WaterMgr->m_WaterTexTimer;
698 double period = 8;
699 int curTex = (int)(time*60/period) % 60;
700 int nexTex = (curTex + 1) % 60;
701
702 float repeatPeriod = WaterMgr->m_RepeatPeriod;
703
704 // Render normals and foam to a framebuffer if we're in fancy effects
705 if (WaterMgr->m_WaterFancyEffects)
706 {
707 // Save the post-processing framebuffer.
708 GLint fbo;
709 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo);
710
711 pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, WaterMgr->m_FancyEffectsFBO);
712
713 glDisable(GL_BLEND);
714 glEnable(GL_DEPTH_TEST);
715 glDepthFunc(GL_LEQUAL);
716
717 glDisable(GL_CULL_FACE);
718 // Overwrite waves that would be behind the ground.
719 CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("glsl/gui_solid", CShaderDefines());
720 dummyShader->Bind();
721
722 dummyShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
723 dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f);
724 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
725 for (size_t i = 0; i < visiblePatches.size(); ++i)
726 {
727 CPatchRData* data = visiblePatches[i];
728 data->RenderWater(dummyShader, true, true);
729 }
730 dummyShader->Unbind();
731
732 glEnable(GL_CULL_FACE);
733 pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
734 }
735 glEnable(GL_BLEND);
736 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
737 glEnable(GL_DEPTH_TEST);
738 glDepthFunc(GL_LEQUAL);
739
740 m->fancyWaterShader->Bind();
741
742 const CCamera& camera = g_Renderer.GetViewCamera();
743 CVector3D camPos = camera.m_Orientation.GetTranslation();
744
745 m->fancyWaterShader->BindTexture(str_normalMap, WaterMgr->m_NormalMap[curTex]);
746 m->fancyWaterShader->BindTexture(str_normalMap2, WaterMgr->m_NormalMap[nexTex]);
747
748 if (WaterMgr->m_WaterFancyEffects)
749 {
750 m->fancyWaterShader->BindTexture(str_waterEffectsTexNorm, WaterMgr->m_FancyTextureNormal);
751 m->fancyWaterShader->BindTexture(str_waterEffectsTexOther, WaterMgr->m_FancyTextureOther);
752 }
753
754 if (WaterMgr->m_WaterRealDepth)
755 m->fancyWaterShader->BindTexture(str_depthTex, WaterMgr->m_depthTT);
756
757 if (WaterMgr->m_WaterRefraction)
758 m->fancyWaterShader->BindTexture(str_refractionMap, WaterMgr->m_RefractionTexture);
759 if (WaterMgr->m_WaterReflection)
760 m->fancyWaterShader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube());
761
762 m->fancyWaterShader->BindTexture(str_reflectionMap, WaterMgr->m_ReflectionTexture);
763 m->fancyWaterShader->BindTexture(str_losMap, losTexture.GetTextureSmooth());
764
765 const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
766
767 m->fancyWaterShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
768
769 //TODO: bind only what's needed
770 if (WaterMgr->m_WaterReflection)
771 {
772 // TODO: check that this rotates in the right direction.
773 CMatrix3D skyBoxRotation;
774 skyBoxRotation.SetIdentity();
775 skyBoxRotation.RotateY(M_PI - 0.3f + lightEnv.GetRotation());
776 m->fancyWaterShader->Uniform(str_skyBoxRot, skyBoxRotation);
777 }
778 m->fancyWaterShader->Uniform(str_sunDir, lightEnv.GetSunDir());
779 m->fancyWaterShader->Uniform(str_sunColor, lightEnv.m_SunColor);
780 m->fancyWaterShader->Uniform(str_color, WaterMgr->m_WaterColor);
781 m->fancyWaterShader->Uniform(str_tint, WaterMgr->m_WaterTint);
782 m->fancyWaterShader->Uniform(str_waviness, WaterMgr->m_Waviness);
783 m->fancyWaterShader->Uniform(str_murkiness, WaterMgr->m_Murkiness);
784 m->fancyWaterShader->Uniform(str_windAngle, WaterMgr->m_WindAngle);
785 m->fancyWaterShader->Uniform(str_repeatScale, 1.0f / repeatPeriod);
786 m->fancyWaterShader->Uniform(str_reflectionMatrix, WaterMgr->m_ReflectionMatrix);
787 m->fancyWaterShader->Uniform(str_refractionMatrix, WaterMgr->m_RefractionMatrix);
788 m->fancyWaterShader->Uniform(str_losMatrix, losTexture.GetTextureMatrix());
789 m->fancyWaterShader->Uniform(str_cameraPos, camPos);
790 m->fancyWaterShader->Uniform(str_fogColor, lightEnv.m_FogColor);
791 m->fancyWaterShader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
792 m->fancyWaterShader->Uniform(str_time, (float)time);
793 m->fancyWaterShader->Uniform(str_screenSize, (float)g_Renderer.GetWidth(), (float)g_Renderer.GetHeight(), 0.0f, 0.0f);
794
795 if (WaterMgr->m_WaterType == L"clap")
796 {
797 m->fancyWaterShader->Uniform(str_waveParams1, 30.0f,1.5f,20.0f,0.03f);
798 m->fancyWaterShader->Uniform(str_waveParams2, 0.5f,0.0f,0.0f,0.0f);
799 }
800 else if (WaterMgr->m_WaterType == L"lake")
801 {
802 m->fancyWaterShader->Uniform(str_waveParams1, 8.5f,1.5f,15.0f,0.03f);
803 m->fancyWaterShader->Uniform(str_waveParams2, 0.2f,0.0f,0.0f,0.07f);
804 }
805 else
806 {
807 m->fancyWaterShader->Uniform(str_waveParams1, 15.0f,0.8f,10.0f,0.1f);
808 m->fancyWaterShader->Uniform(str_waveParams2, 0.3f,0.0f,0.1f,0.3f);
809 }
810
811 if (shadow && WaterMgr->m_WaterShadows)
812 {
813 m->fancyWaterShader->BindTexture(str_shadowTex, shadow->GetTexture());
814 m->fancyWaterShader->Uniform(str_shadowTransform, shadow->GetTextureMatrix());
815 int width = shadow->GetWidth();
816 int height = shadow->GetHeight();
817 m->fancyWaterShader->Uniform(str_shadowScale, width, height, 1.0f / width, 1.0f / height);
818 }
819
820 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
821 for (size_t i = 0; i < visiblePatches.size(); ++i)
822 {
823 CPatchRData* data = visiblePatches[i];
824 data->RenderWater(m->fancyWaterShader);
825 }
826 m->fancyWaterShader->Unbind();
827
828 glDepthFunc(GL_LEQUAL);
829 glDisable(GL_BLEND);
830
831 return true;
832 }
833
RenderSimpleWater(int cullGroup)834 void TerrainRenderer::RenderSimpleWater(int cullGroup)
835 {
836 #if CONFIG2_GLES
837 UNUSED2(cullGroup);
838 #else
839 PROFILE3_GPU("simple water");
840
841 WaterManager* WaterMgr = g_Renderer.GetWaterManager();
842 CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
843
844 glEnable(GL_DEPTH_TEST);
845 glDepthFunc(GL_LEQUAL);
846
847 double time = WaterMgr->m_WaterTexTimer;
848 double period = 1.6f;
849 int curTex = (int)(time*60/period) % 60;
850
851 WaterMgr->m_WaterTexture[curTex]->Bind();
852
853 // Shift the texture coordinates by these amounts to make the water "flow"
854 float tx = -fmod(time, 81.0)/81.0;
855 float ty = -fmod(time, 34.0)/34.0;
856 float repeatPeriod = 16.0f;
857
858 // Perform the shifting by using texture coordinate generation
859 GLfloat texgenS0[4] = { 1/repeatPeriod, 0, 0, tx };
860 GLfloat texgenT0[4] = { 0, 0, 1/repeatPeriod, ty };
861 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
862 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
863 glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS0);
864 glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT0);
865 glEnable(GL_TEXTURE_GEN_S);
866 glEnable(GL_TEXTURE_GEN_T);
867
868 // Set up texture environment to multiply vertex RGB by texture RGB.
869 GLfloat waterColor[4] = { WaterMgr->m_WaterColor.r, WaterMgr->m_WaterColor.g, WaterMgr->m_WaterColor.b, 1.0f };
870 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, waterColor);
871 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
872 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
873 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
874 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
875 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT);
876 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
877
878
879 // Multiply by LOS texture
880 losTexture.BindTexture(1);
881 CMatrix3D losMatrix = losTexture.GetTextureMatrix();
882 GLfloat texgenS1[4] = { losMatrix[0], losMatrix[4], losMatrix[8], losMatrix[12] };
883 GLfloat texgenT1[4] = { losMatrix[1], losMatrix[5], losMatrix[9], losMatrix[13] };
884 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
885 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
886 glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS1);
887 glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT1);
888 glEnable(GL_TEXTURE_GEN_S);
889 glEnable(GL_TEXTURE_GEN_T);
890
891 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
892 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
893 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
894 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
895 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
896 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
897
898 CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("fixed:dummy", CShaderDefines());
899 dummyShader->Bind();
900
901 glEnableClientState(GL_VERTEX_ARRAY);
902
903 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
904 for (size_t i = 0; i < visiblePatches.size(); ++i)
905 {
906 CPatchRData* data = visiblePatches[i];
907 data->RenderWater(dummyShader, false, true);
908 }
909
910 glDisableClientState(GL_VERTEX_ARRAY);
911
912 dummyShader->Unbind();
913
914 g_Renderer.BindTexture(1, 0);
915
916 glDisable(GL_TEXTURE_GEN_S);
917 glDisable(GL_TEXTURE_GEN_T);
918 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
919
920 pglActiveTextureARB(GL_TEXTURE0_ARB);
921
922 // Clean up the texture matrix and blend mode
923 glDisable(GL_TEXTURE_GEN_S);
924 glDisable(GL_TEXTURE_GEN_T);
925 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
926
927 glDisable(GL_TEXTURE_2D);
928 #endif
929 }
930
931 ///////////////////////////////////////////////////////////////////
932 // Render water that is part of the terrain
RenderWater(const CShaderDefines & context,int cullGroup,ShadowMap * shadow)933 void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
934 {
935 WaterManager* WaterMgr = g_Renderer.GetWaterManager();
936
937 WaterMgr->UpdateQuality();
938
939 if (!WaterMgr->WillRenderFancyWater())
940 RenderSimpleWater(cullGroup);
941 else
942 RenderFancyWater(context, cullGroup, shadow);
943 }
944
RenderPriorities(int cullGroup)945 void TerrainRenderer::RenderPriorities(int cullGroup)
946 {
947 PROFILE("priorities");
948
949 ENSURE(m->phase == Phase_Render);
950
951 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
952 tech->BeginPass();
953 CTextRenderer textRenderer(tech->GetShader());
954
955 textRenderer.Font(CStrIntern("mono-stroke-10"));
956 textRenderer.Color(1.0f, 1.0f, 0.0f);
957
958 std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
959 for (size_t i = 0; i < visiblePatches.size(); ++i)
960 visiblePatches[i]->RenderPriorities(textRenderer);
961
962 textRenderer.Render();
963 tech->EndPass();
964 }
965