1 /*
2  Copyright (c) 2013 yvt
3 
4  This file is part of OpenSpades.
5 
6  OpenSpades is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OpenSpades is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OpenSpades.  If not, see <http://www.gnu.org/licenses/>.
18 
19  */
20 
21 #include "GLMapRenderer.h"
22 #include <Client/GameMap.h>
23 #include <Core/Debug.h>
24 #include <Core/Settings.h>
25 #include "GLDynamicLightShader.h"
26 #include "GLImage.h"
27 #include "GLMapChunk.h"
28 #include "GLMapShadowRenderer.h"
29 #include "GLProfiler.h"
30 #include "GLProgram.h"
31 #include "GLProgram.h"
32 #include "GLProgramAttribute.h"
33 #include "GLProgramUniform.h"
34 #include "GLRenderer.h"
35 #include "GLShadowShader.h"
36 #include "IGLDevice.h"
37 
38 namespace spades {
39 	namespace draw {
PreloadShaders(spades::draw::GLRenderer * renderer)40 		void GLMapRenderer::PreloadShaders(spades::draw::GLRenderer *renderer) {
41 			if (renderer->GetSettings().r_physicalLighting)
42 				renderer->RegisterProgram("Shaders/BasicBlockPhys.program");
43 			else
44 				renderer->RegisterProgram("Shaders/BasicBlock.program");
45 			renderer->RegisterProgram("Shaders/BasicBlockDepthOnly.program");
46 			renderer->RegisterProgram("Shaders/BasicBlockDynamicLit.program");
47 			renderer->RegisterProgram("Shaders/BackFaceBlock.program");
48 			renderer->RegisterImage("Gfx/AmbientOcclusion.png");
49 		}
50 
GLMapRenderer(client::GameMap * m,GLRenderer * r)51 		GLMapRenderer::GLMapRenderer(client::GameMap *m, GLRenderer *r) : renderer(r), gameMap(m) {
52 			SPADES_MARK_FUNCTION();
53 
54 			device = renderer->GetGLDevice();
55 
56 			numChunkWidth = gameMap->Width() / GLMapChunk::Size;
57 			numChunkHeight = gameMap->Height() / GLMapChunk::Size;
58 			numChunkDepth = gameMap->Depth() / GLMapChunk::Size;
59 
60 			numChunks = numChunkWidth * numChunkHeight * numChunkDepth;
61 
62 			chunks = new GLMapChunk *[numChunks];
63 			chunkInfos = new ChunkRenderInfo[numChunks];
64 
65 			for (int i = 0; i < numChunks; i++)
66 				chunks[i] = new GLMapChunk(this, gameMap, i / numChunkDepth / numChunkHeight,
67 				                           (i / numChunkDepth) % numChunkHeight, i % numChunkDepth);
68 
69 			if (r->GetSettings().r_physicalLighting)
70 				basicProgram = renderer->RegisterProgram("Shaders/BasicBlockPhys.program");
71 			else
72 				basicProgram = renderer->RegisterProgram("Shaders/BasicBlock.program");
73 			depthonlyProgram = renderer->RegisterProgram("Shaders/BasicBlockDepthOnly.program");
74 			dlightProgram = renderer->RegisterProgram("Shaders/BasicBlockDynamicLit.program");
75 			backfaceProgram = renderer->RegisterProgram("Shaders/BackFaceBlock.program");
76 			aoImage = (GLImage *)renderer->RegisterImage("Gfx/AmbientOcclusion.png");
77 
78 			static const uint8_t squareVertices[] = {0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1};
79 			squareVertexBuffer = device->GenBuffer();
80 			device->BindBuffer(IGLDevice::ArrayBuffer, squareVertexBuffer);
81 			device->BufferData(IGLDevice::ArrayBuffer, sizeof(squareVertices), squareVertices,
82 			                   IGLDevice::StaticDraw);
83 			device->BindBuffer(IGLDevice::ArrayBuffer, 0);
84 		}
85 
~GLMapRenderer()86 		GLMapRenderer::~GLMapRenderer() {
87 			SPADES_MARK_FUNCTION();
88 
89 			device->DeleteBuffer(squareVertexBuffer);
90 			for (int i = 0; i < numChunks; i++)
91 				delete chunks[i];
92 			delete[] chunks;
93 			delete[] chunkInfos;
94 		}
GameMapChanged(int x,int y,int z,client::GameMap * map)95 		void GLMapRenderer::GameMapChanged(int x, int y, int z, client::GameMap *map) {
96 			SPADES_MARK_FUNCTION_DEBUG();
97 
98 			/*GetChunk(x >> GLMapChunk::SizeBits,
99 			         y >> GLMapChunk::SizeBits,
100 			         z >> GLMapChunk::SizeBits)->SetNeedsUpdate();*/
101 			// int fx = x & (GLMapChunk::Size - 1);
102 			// int fy = y & (GLMapChunk::Size - 1);
103 			int fz = z & (GLMapChunk::Size - 1);
104 			int sx = -1;
105 			int sy = -1;
106 			int sz = fz == 0 ? -1 : 0;
107 			int ex = 1;
108 			int ey = 1;
109 			int ez = fz == (GLMapChunk::Size - 1) ? 1 : 0;
110 			for (int cx = sx; cx <= ex; cx++)
111 				for (int cy = sy; cy <= ey; cy++)
112 					for (int cz = sz; cz <= ez; cz++) {
113 						int xx = x + cx, yy = y + cy, zz = z + cz;
114 						xx >>= GLMapChunk::SizeBits;
115 						yy >>= GLMapChunk::SizeBits;
116 						zz >>= GLMapChunk::SizeBits;
117 						xx &= numChunkWidth - 1;
118 						yy &= numChunkHeight - 1;
119 						if (xx >= 0 && yy >= 0 && zz >= 0 && xx < numChunkWidth &&
120 						    yy < numChunkHeight && zz < numChunkDepth) {
121 							GetChunk(xx, yy, zz)->SetNeedsUpdate();
122 						}
123 					}
124 		}
125 
RealizeChunks(spades::Vector3 eye)126 		void GLMapRenderer::RealizeChunks(spades::Vector3 eye) {
127 			SPADES_MARK_FUNCTION();
128 
129 			float cullDistance = 128.f;
130 			float releaseDistance = 160.f;
131 			for (int i = 0; i < numChunks; i++) {
132 				float dist = chunks[i]->DistanceFromEye(eye);
133 				chunkInfos[i].distance = dist;
134 				if (dist < cullDistance)
135 					chunks[i]->SetRealized(true);
136 				else if (dist > releaseDistance)
137 					chunks[i]->SetRealized(false);
138 			}
139 		}
140 
Realize()141 		void GLMapRenderer::Realize() {
142 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Map Chunks");
143 
144 			Vector3 eye = renderer->GetSceneDef().viewOrigin;
145 			RealizeChunks(eye);
146 		}
147 
Prerender()148 		void GLMapRenderer::Prerender() {
149 			SPADES_MARK_FUNCTION();
150 			//depth-only pass
151 
152 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Map");
153 			Vector3 eye = renderer->GetSceneDef().viewOrigin;
154 
155 			device->Enable(IGLDevice::CullFace, true);
156 			device->Enable(IGLDevice::DepthTest, true);
157 			device->ColorMask(false, false, false, false);
158 
159 			depthonlyProgram->Use();
160 			static GLProgramAttribute positionAttribute("positionAttribute");
161 			positionAttribute(depthonlyProgram);
162 			device->EnableVertexAttribArray(positionAttribute(), true);
163 			static GLProgramUniform projectionViewMatrix("projectionViewMatrix");
164 			projectionViewMatrix(depthonlyProgram);
165 			projectionViewMatrix.SetValue(renderer->GetProjectionViewMatrix());
166 
167 			// draw from nearest to farthest
168 			int cx = (int)floorf(eye.x) / GLMapChunk::Size;
169 			int cy = (int)floorf(eye.y) / GLMapChunk::Size;
170 			int cz = (int)floorf(eye.z) / GLMapChunk::Size;
171 			DrawColumnDepth(cx, cy, cz, eye);
172 			for (int dist = 1; dist <= 128 / GLMapChunk::Size; dist++) {
173 				for (int x = cx - dist; x <= cx + dist; x++) {
174 					DrawColumnDepth(x, cy + dist, cz, eye);
175 					DrawColumnDepth(x, cy - dist, cz, eye);
176 				}
177 				for (int y = cy - dist + 1; y <= cy + dist - 1; y++) {
178 					DrawColumnDepth(cx + dist, y, cz, eye);
179 					DrawColumnDepth(cx - dist, y, cz, eye);
180 				}
181 			}
182 
183 
184 			device->EnableVertexAttribArray(positionAttribute(), false);
185 			device->ColorMask(true, true, true, true);
186 
187 		}
188 
RenderSunlightPass()189 		void GLMapRenderer::RenderSunlightPass() {
190 			SPADES_MARK_FUNCTION();
191 
192 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Map");
193 
194 			Vector3 eye = renderer->GetSceneDef().viewOrigin;
195 
196 			// draw back face to avoid cheating.
197 			// without this, players can see through blocks by
198 			// covering themselves by ones.
199 			RenderBackface();
200 
201 			device->ActiveTexture(0);
202 			aoImage->Bind(IGLDevice::Texture2D);
203 			device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter,
204 			                     IGLDevice::Linear);
205 
206 			device->ActiveTexture(1);
207 			device->BindTexture(IGLDevice::Texture2D, 0);
208 
209 			device->Enable(IGLDevice::CullFace, true);
210 			device->Enable(IGLDevice::DepthTest, true);
211 
212 			basicProgram->Use();
213 
214 			static GLShadowShader shadowShader;
215 			shadowShader(renderer, basicProgram, 2);
216 
217 			static GLProgramUniform fogDistance("fogDistance");
218 			fogDistance(basicProgram);
219 			fogDistance.SetValue(renderer->GetFogDistance());
220 
221 			static GLProgramUniform viewSpaceLight("viewSpaceLight");
222 			viewSpaceLight(basicProgram);
223 			Vector3 vspLight = (renderer->GetViewMatrix() * MakeVector4(0, -1, -1, 0)).GetXYZ();
224 			viewSpaceLight.SetValue(vspLight.x, vspLight.y, vspLight.z);
225 
226 			static GLProgramUniform fogColor("fogColor");
227 			fogColor(basicProgram);
228 			Vector3 fogCol = renderer->GetFogColorForSolidPass();
229 			fogCol *= fogCol; // linearize
230 			fogColor.SetValue(fogCol.x, fogCol.y, fogCol.z);
231 
232 			static GLProgramUniform aoUniform("ambientOcclusionTexture");
233 			aoUniform(basicProgram);
234 			aoUniform.SetValue(0);
235 
236 			static GLProgramUniform detailTextureUnif("detailTexture");
237 			detailTextureUnif(basicProgram);
238 			detailTextureUnif.SetValue(1);
239 
240 			device->BindBuffer(IGLDevice::ArrayBuffer, 0);
241 
242 			static GLProgramAttribute positionAttribute("positionAttribute");
243 			static GLProgramAttribute ambientOcclusionCoordAttribute(
244 			  "ambientOcclusionCoordAttribute");
245 			static GLProgramAttribute colorAttribute("colorAttribute");
246 			static GLProgramAttribute normalAttribute("normalAttribute");
247 			static GLProgramAttribute fixedPositionAttribute("fixedPositionAttribute");
248 
249 			positionAttribute(basicProgram);
250 			ambientOcclusionCoordAttribute(basicProgram);
251 			colorAttribute(basicProgram);
252 			normalAttribute(basicProgram);
253 			fixedPositionAttribute(basicProgram);
254 
255 			device->EnableVertexAttribArray(positionAttribute(), true);
256 			if (ambientOcclusionCoordAttribute() != -1)
257 				device->EnableVertexAttribArray(ambientOcclusionCoordAttribute(), true);
258 			device->EnableVertexAttribArray(colorAttribute(), true);
259 			if (normalAttribute() != -1)
260 				device->EnableVertexAttribArray(normalAttribute(), true);
261 			device->EnableVertexAttribArray(fixedPositionAttribute(), true);
262 
263 			static GLProgramUniform projectionViewMatrix("projectionViewMatrix");
264 			projectionViewMatrix(basicProgram);
265 			projectionViewMatrix.SetValue(renderer->GetProjectionViewMatrix());
266 
267 			static GLProgramUniform viewMatrix("viewMatrix");
268 			viewMatrix(basicProgram);
269 			viewMatrix.SetValue(renderer->GetViewMatrix());
270 
271 			static GLProgramUniform viewOriginVector("viewOriginVector");
272 			viewOriginVector(basicProgram);
273 			const auto &viewOrigin = renderer->GetSceneDef().viewOrigin;
274 			viewOriginVector.SetValue(viewOrigin.x, viewOrigin.y, viewOrigin.z);
275 
276 			//RealizeChunks(eye); // should already be realized from the prepass
277 			//TODO maybe add some way of checking if the chunks have been realized for the current eye? Probably just a bool called "alreadyrealized" that gets checked in RealizeChunks
278 
279 			// draw from nearest to farthest
280 			int cx = (int)floorf(eye.x) / GLMapChunk::Size;
281 			int cy = (int)floorf(eye.y) / GLMapChunk::Size;
282 			int cz = (int)floorf(eye.z) / GLMapChunk::Size;
283 			DrawColumnSunlight(cx, cy, cz, eye);
284 			for (int dist = 1; dist <= 128 / GLMapChunk::Size; dist++) {
285 				for (int x = cx - dist; x <= cx + dist; x++) {
286 					DrawColumnSunlight(x, cy + dist, cz, eye);
287 					DrawColumnSunlight(x, cy - dist, cz, eye);
288 				}
289 				for (int y = cy - dist + 1; y <= cy + dist - 1; y++) {
290 					DrawColumnSunlight(cx + dist, y, cz, eye);
291 					DrawColumnSunlight(cx - dist, y, cz, eye);
292 				}
293 			}
294 
295 			device->EnableVertexAttribArray(positionAttribute(), false);
296 			if (ambientOcclusionCoordAttribute() != -1)
297 				device->EnableVertexAttribArray(ambientOcclusionCoordAttribute(), false);
298 			device->EnableVertexAttribArray(colorAttribute(), false);
299 			if (normalAttribute() != -1)
300 				device->EnableVertexAttribArray(normalAttribute(), false);
301 			device->EnableVertexAttribArray(fixedPositionAttribute(), false);
302 
303 			device->ActiveTexture(1);
304 			device->BindTexture(IGLDevice::Texture2D, 0);
305 			device->ActiveTexture(0);
306 			device->BindTexture(IGLDevice::Texture2D, 0);
307 		}
308 
RenderDynamicLightPass(std::vector<GLDynamicLight> lights)309 		void GLMapRenderer::RenderDynamicLightPass(std::vector<GLDynamicLight> lights) {
310 			SPADES_MARK_FUNCTION();
311 
312 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Map");
313 
314 			if (lights.empty())
315 				return;
316 
317 			Vector3 eye = renderer->GetSceneDef().viewOrigin;
318 
319 			device->ActiveTexture(0);
320 			device->BindTexture(IGLDevice::Texture2D, 0);
321 
322 			device->Enable(IGLDevice::CullFace, true);
323 			device->Enable(IGLDevice::DepthTest, true);
324 
325 			dlightProgram->Use();
326 
327 			static GLProgramUniform fogDistance("fogDistance");
328 			fogDistance(dlightProgram);
329 			fogDistance.SetValue(renderer->GetFogDistance());
330 
331 			static GLProgramUniform detailTextureUnif("detailTexture");
332 			detailTextureUnif(dlightProgram);
333 			detailTextureUnif.SetValue(0);
334 
335 			device->BindBuffer(IGLDevice::ArrayBuffer, 0);
336 
337 			static GLProgramAttribute positionAttribute("positionAttribute");
338 			static GLProgramAttribute colorAttribute("colorAttribute");
339 			static GLProgramAttribute normalAttribute("normalAttribute");
340 
341 			positionAttribute(dlightProgram);
342 			colorAttribute(dlightProgram);
343 			normalAttribute(dlightProgram);
344 
345 			device->EnableVertexAttribArray(positionAttribute(), true);
346 			device->EnableVertexAttribArray(colorAttribute(), true);
347 			device->EnableVertexAttribArray(normalAttribute(), true);
348 
349 			static GLProgramUniform projectionViewMatrix("projectionViewMatrix");
350 			projectionViewMatrix(dlightProgram);
351 			projectionViewMatrix.SetValue(renderer->GetProjectionViewMatrix());
352 
353 			static GLProgramUniform viewMatrix("viewMatrix");
354 			viewMatrix(dlightProgram);
355 			viewMatrix.SetValue(renderer->GetViewMatrix());
356 
357 			static GLProgramUniform viewOriginVector("viewOriginVector");
358 			viewOriginVector(dlightProgram);
359 			const auto &viewOrigin = renderer->GetSceneDef().viewOrigin;
360 			viewOriginVector.SetValue(viewOrigin.x, viewOrigin.y, viewOrigin.z);
361 
362 			//RealizeChunks(eye); // should already be realized from the prepass
363 
364 			// draw from nearest to farthest
365 			int cx = (int)floorf(eye.x) / GLMapChunk::Size;
366 			int cy = (int)floorf(eye.y) / GLMapChunk::Size;
367 			int cz = (int)floorf(eye.z) / GLMapChunk::Size;
368 			DrawColumnDLight(cx, cy, cz, eye, lights);
369 			// TODO: optimize call
370 			//       ex. don't call a chunk'r render method if
371 			//           no dlight lights it
372 			for (int dist = 1; dist <= 128 / GLMapChunk::Size; dist++) {
373 				for (int x = cx - dist; x <= cx + dist; x++) {
374 					DrawColumnDLight(x, cy + dist, cz, eye, lights);
375 					DrawColumnDLight(x, cy - dist, cz, eye, lights);
376 				}
377 				for (int y = cy - dist + 1; y <= cy + dist - 1; y++) {
378 					DrawColumnDLight(cx + dist, y, cz, eye, lights);
379 					DrawColumnDLight(cx - dist, y, cz, eye, lights);
380 				}
381 			}
382 
383 			device->EnableVertexAttribArray(positionAttribute(), false);
384 			device->EnableVertexAttribArray(colorAttribute(), false);
385 			device->EnableVertexAttribArray(normalAttribute(), false);
386 
387 			device->ActiveTexture(0);
388 			device->BindTexture(IGLDevice::Texture2D, 0);
389 		}
390 
DrawColumnDepth(int cx,int cy,int cz,spades::Vector3 eye)391 		void GLMapRenderer::DrawColumnDepth(int cx, int cy, int cz, spades::Vector3 eye) {
392 			cx &= numChunkWidth - 1;
393 			cy &= numChunkHeight - 1;
394 			for (int z = std::max(cz, 0); z < numChunkDepth; z++)
395 				GetChunk(cx, cy, z)->RenderDepthPass();
396 			for (int z = std::min(cz - 1, 63); z >= 0; z--)
397 				GetChunk(cx, cy, z)->RenderDepthPass();
398 		}
DrawColumnSunlight(int cx,int cy,int cz,spades::Vector3 eye)399 		void GLMapRenderer::DrawColumnSunlight(int cx, int cy, int cz, spades::Vector3 eye) {
400 			cx &= numChunkWidth - 1;
401 			cy &= numChunkHeight - 1;
402 			for (int z = std::max(cz, 0); z < numChunkDepth; z++)
403 				GetChunk(cx, cy, z)->RenderSunlightPass();
404 			for (int z = std::min(cz - 1, 63); z >= 0; z--)
405 				GetChunk(cx, cy, z)->RenderSunlightPass();
406 		}
407 
DrawColumnDLight(int cx,int cy,int cz,spades::Vector3 eye,const std::vector<GLDynamicLight> & lights)408 		void GLMapRenderer::DrawColumnDLight(int cx, int cy, int cz, spades::Vector3 eye,
409 		                                     const std::vector<GLDynamicLight> &lights) {
410 			cx &= numChunkWidth - 1;
411 			cy &= numChunkHeight - 1;
412 			for (int z = std::max(cz, 0); z < numChunkDepth; z++)
413 				GetChunk(cx, cy, z)->RenderDLightPass(lights);
414 			for (int z = std::min(cz - 1, 63); z >= 0; z--)
415 				GetChunk(cx, cy, z)->RenderDLightPass(lights);
416 		}
417 
418 #pragma mark - BackFaceBlock
419 
420 		struct BFVertex {
421 			int16_t x, y, z;
422 			uint16_t pad;
423 
Makespades::draw::BFVertex424 			static BFVertex Make(int x, int y, int z) {
425 				BFVertex v = {(int16_t)x, (int16_t)y, (int16_t)z, 0};
426 				return v;
427 			}
428 		};
429 
EmitBackFace(int x,int y,int z,int ux,int uy,int uz,int vx,int vy,int vz,std::vector<BFVertex> & vertices,std::vector<uint16_t> & indices)430 		static void EmitBackFace(int x, int y, int z, int ux, int uy, int uz, int vx, int vy,
431 		                         int vz, std::vector<BFVertex> &vertices,
432 		                         std::vector<uint16_t> &indices) {
433 			uint16_t idx = (uint16_t)vertices.size();
434 
435 			vertices.push_back(BFVertex::Make(x, y, z));
436 			vertices.push_back(BFVertex::Make(x + ux, y + uy, z + uz));
437 			vertices.push_back(BFVertex::Make(x + vx, y + vy, z + vz));
438 			vertices.push_back(BFVertex::Make(x + ux + vx, y + uy + vy, z + uz + vz));
439 
440 			indices.push_back(idx);
441 			indices.push_back(idx + 1);
442 			indices.push_back(idx + 2);
443 			indices.push_back(idx + 1);
444 			indices.push_back(idx + 3);
445 			indices.push_back(idx + 2);
446 		}
447 
RenderBackface()448 		void GLMapRenderer::RenderBackface() {
449 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Back-face");
450 
451 			IntVector3 eye = renderer->GetSceneDef().viewOrigin.Floor();
452 			std::vector<BFVertex> vertices;
453 			std::vector<uint16_t> indices;
454 			client::GameMap *m = gameMap;
455 
456 			int x, y, z;
457 			const int range = 1;
458 			for (x = eye.x - range; x <= eye.x + range; x++) {
459 				for (y = eye.y - range; y <= eye.y + range; y++) {
460 					for (z = eye.z - range; z <= eye.z + range; z++) {
461 						if (z >= 63)
462 							continue;
463 						if (z < 0)
464 							continue;
465 						if (!m->IsSolidWrapped(x, y, z))
466 							continue;
467 						SPAssert(m->IsSolidWrapped(x, y, z));
468 
469 						if (m->IsSolidWrapped(x - 1, y, z)) {
470 							EmitBackFace(x, y, z, 0, 1, 0, 0, 0, 1, vertices, indices);
471 						}
472 						if (m->IsSolidWrapped(x + 1, y, z)) {
473 							EmitBackFace(x + 1, y, z, 0, 1, 0, 0, 0, 1, vertices, indices);
474 						}
475 						if (m->IsSolidWrapped(x, y - 1, z)) {
476 							EmitBackFace(x, y, z, 1, 0, 0, 0, 0, 1, vertices, indices);
477 						}
478 						if (m->IsSolidWrapped(x, y + 1, z)) {
479 							EmitBackFace(x, y + 1, z, 1, 0, 0, 0, 0, 1, vertices, indices);
480 						}
481 						if (m->IsSolidWrapped(x, y, z - 1)) {
482 							EmitBackFace(x, y, z, 1, 0, 0, 0, 1, 0, vertices, indices);
483 						}
484 						if (m->IsSolidWrapped(x, y, z + 1)) {
485 							EmitBackFace(x, y, z + 1, 1, 0, 0, 0, 1, 0, vertices, indices);
486 						}
487 					}
488 				}
489 			}
490 
491 			if (vertices.empty())
492 				return;
493 
494 			device->Enable(IGLDevice::CullFace, false);
495 
496 			backfaceProgram->Use();
497 
498 			static GLProgramAttribute positionAttribute("positionAttribute");
499 			static GLProgramUniform projectionViewMatrix("projectionViewMatrix");
500 
501 			positionAttribute(backfaceProgram);
502 			projectionViewMatrix(backfaceProgram);
503 
504 			projectionViewMatrix.SetValue(renderer->GetProjectionViewMatrix());
505 
506 			device->BindBuffer(IGLDevice::ArrayBuffer, 0);
507 			device->VertexAttribPointer(positionAttribute(), 3, IGLDevice::Short, false,
508 			                            sizeof(BFVertex), vertices.data());
509 
510 			device->EnableVertexAttribArray(positionAttribute(), true);
511 
512 			device->BindBuffer(IGLDevice::ElementArrayBuffer, 0);
513 			device->DrawElements(IGLDevice::Triangles,
514 			                     static_cast<IGLDevice::Sizei>(indices.size()),
515 			                     IGLDevice::UnsignedShort, indices.data());
516 
517 			device->EnableVertexAttribArray(positionAttribute(), false);
518 
519 			device->Enable(IGLDevice::CullFace, true);
520 		}
521 	}
522 }
523