OpenClonk
C4DrawMeshGL.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2013-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 /* OpenGL implementation of Mesh Rendering */
18 
19 #include "C4Include.h"
21 
22 #include "graphics/C4DrawGL.h"
25 #include "lib/SHA1.h"
26 #include "lib/StdMesh.h"
27 #include "object/C4Object.h"
29 
30 #include <clocale>
31 
32 #ifndef USE_CONSOLE
33 
34 namespace
35 {
36  template<int category>
37  class ScopedLocale
38  {
39  // We need to make a copy of the return value of setlocale, because
40  // it's using TLS
41  std::string saved_loc;
42  public:
43  explicit ScopedLocale(const char *locale)
44  {
45  const char *old_loc = setlocale(category, locale);
46  if (old_loc == nullptr)
47  throw std::invalid_argument("Argument to setlocale was invalid");
48  saved_loc = old_loc;
49  }
50  ~ScopedLocale()
51  {
52  setlocale(category, saved_loc.c_str());
53  }
54  };
55 
57  // Shader code generation
58  // This translates the fixed function instructions in a material script
59  // to an equivalent fragment shader. The generated code can certainly
60  // be optimized more.
62  StdStrBuf Texture2DToCode(int index, bool hasTextureAnimation)
63  {
64  if (hasTextureAnimation) return FormatString("texture(oc_Texture%d, (oc_TextureMatrix%d * vec4(texcoord, 0.0, 1.0)).xy)", index, index);
65  return FormatString("texture(oc_Texture%d, texcoord)", index);
66  }
67 
68  StdStrBuf TextureUnitSourceToCode(int index, StdMeshMaterialTextureUnit::BlendOpSourceType source, const float manualColor[3], float manualAlpha, bool hasTextureAnimation)
69  {
70  switch(source)
71  {
72  case StdMeshMaterialTextureUnit::BOS_Current: return StdStrBuf("currentColor");
73  case StdMeshMaterialTextureUnit::BOS_Texture: return Texture2DToCode(index, hasTextureAnimation);
74  case StdMeshMaterialTextureUnit::BOS_Diffuse: return StdStrBuf("diffuse");
75  case StdMeshMaterialTextureUnit::BOS_Specular: return StdStrBuf("diffuse"); // TODO: Should be specular
76  case StdMeshMaterialTextureUnit::BOS_PlayerColor: return StdStrBuf("vec4(oc_PlayerColor, 1.0)");
77  case StdMeshMaterialTextureUnit::BOS_Manual: return FormatString("vec4(%f, %f, %f, %f)", manualColor[0], manualColor[1], manualColor[2], manualAlpha);
78  default: assert(false); return StdStrBuf("vec4(0.0, 0.0, 0.0, 0.0)");
79  }
80  }
81 
82  StdStrBuf TextureUnitBlendToCode(int index, StdMeshMaterialTextureUnit::BlendOpExType blend_type, const char* source1, const char* source2, float manualFactor, bool hasTextureAnimation)
83  {
84  switch(blend_type)
85  {
88  case StdMeshMaterialTextureUnit::BOX_Modulate: return FormatString("%s * %s", source1, source2);
89  case StdMeshMaterialTextureUnit::BOX_ModulateX2: return FormatString("2.0 * %s * %s", source1, source2);
90  case StdMeshMaterialTextureUnit::BOX_ModulateX4: return FormatString("4.0 * %s * %s", source1, source2);
91  case StdMeshMaterialTextureUnit::BOX_Add: return FormatString("%s + %s", source1, source2);
92  case StdMeshMaterialTextureUnit::BOX_AddSigned: return FormatString("%s + %s - 0.5", source1, source2);
93  case StdMeshMaterialTextureUnit::BOX_AddSmooth: return FormatString("%s + %s - %s*%s", source1, source2, source1, source2);
94  case StdMeshMaterialTextureUnit::BOX_Subtract: return FormatString("%s - %s", source1, source2);
95  case StdMeshMaterialTextureUnit::BOX_BlendDiffuseAlpha: return FormatString("diffuse.a * %s + (1.0 - diffuse.a) * %s", source1, source2);
96  case StdMeshMaterialTextureUnit::BOX_BlendTextureAlpha: return FormatString("%s.a * %s + (1.0 - %s.a) * %s", Texture2DToCode(index, hasTextureAnimation).getData(), source1, Texture2DToCode(index, hasTextureAnimation).getData(), source2);
97  case StdMeshMaterialTextureUnit::BOX_BlendCurrentAlpha: return FormatString("currentColor.a * %s + (1.0 - currentColor.a) * %s", source1, source2);
98  case StdMeshMaterialTextureUnit::BOX_BlendManual: return FormatString("%f * %s + (1.0 - %f) * %s", manualFactor, source1, manualFactor, source2);
99  case StdMeshMaterialTextureUnit::BOX_Dotproduct: return FormatString("vec3(4.0 * dot(%s - 0.5, %s - 0.5), 4.0 * dot(%s - 0.5, %s - 0.5), 4.0 * dot(%s - 0.5, %s - 0.5))", source1, source2, source1, source2, source1, source2); // TODO: Needs special handling for the case of alpha
100  case StdMeshMaterialTextureUnit::BOX_BlendDiffuseColor: return FormatString("diffuse.rgb * %s + (1.0 - diffuse.rgb) * %s", source1, source2);
101  default: assert(false); return StdStrBuf(source1);
102  }
103  }
104 
105  StdStrBuf TextureUnitToCode(int index, const StdMeshMaterialTextureUnit& texunit)
106  {
107  ScopedLocale<LC_NUMERIC> scoped_c_locale("C");
108  const bool hasTextureAnimation = texunit.HasTexCoordAnimation();
109 
110  StdStrBuf color_source1 = FormatString("%s.rgb", TextureUnitSourceToCode(index, texunit.ColorOpSources[0], texunit.ColorOpManualColor1, texunit.AlphaOpManualAlpha1, hasTextureAnimation).getData());
111  StdStrBuf color_source2 = FormatString("%s.rgb", TextureUnitSourceToCode(index, texunit.ColorOpSources[1], texunit.ColorOpManualColor2, texunit.AlphaOpManualAlpha2, hasTextureAnimation).getData());
112  StdStrBuf alpha_source1 = FormatString("%s.a", TextureUnitSourceToCode(index, texunit.AlphaOpSources[0], texunit.ColorOpManualColor1, texunit.AlphaOpManualAlpha1, hasTextureAnimation).getData());
113  StdStrBuf alpha_source2 = FormatString("%s.a", TextureUnitSourceToCode(index, texunit.AlphaOpSources[1], texunit.ColorOpManualColor2, texunit.AlphaOpManualAlpha2, hasTextureAnimation).getData());
114 
115  return FormatString("currentColor = vec4(%s, %s);\n", TextureUnitBlendToCode(index, texunit.ColorOpEx, color_source1.getData(), color_source2.getData(), texunit.ColorOpManualFactor, hasTextureAnimation).getData(), TextureUnitBlendToCode(index, texunit.AlphaOpEx, alpha_source1.getData(), alpha_source2.getData(), texunit.AlphaOpManualFactor, hasTextureAnimation).getData());
116  }
117 
118  StdStrBuf AlphaTestToCode(const StdMeshMaterialPass& pass)
119  {
120  ScopedLocale<LC_NUMERIC> scoped_c_locale("C");
121  switch (pass.AlphaRejectionFunction)
122  {
124  return StdStrBuf("");
126  return StdStrBuf("discard;");
128  return FormatString("if (!(fragColor.a < %f)) discard;", pass.AlphaRejectionValue);
130  return FormatString("if (!(fragColor.a <= %f)) discard;", pass.AlphaRejectionValue);
132  return FormatString("if (!(fragColor.a == %f)) discard;", pass.AlphaRejectionValue);
134  return FormatString("if (!(fragColor.a != %f)) discard;", pass.AlphaRejectionValue);
136  return FormatString("if (!(fragColor.a > %f)) discard;", pass.AlphaRejectionValue);
138  return FormatString("if (!(fragColor.a >= %f)) discard;", pass.AlphaRejectionValue);
139  default:
140  assert(false);
141  return StdStrBuf();
142  }
143  }
144 
145  // Simple helper function
146  inline GLenum OgreBlendTypeToGL(StdMeshMaterialPass::SceneBlendType blend)
147  {
148  switch(blend)
149  {
150  case StdMeshMaterialPass::SB_One: return GL_ONE;
151  case StdMeshMaterialPass::SB_Zero: return GL_ZERO;
152  case StdMeshMaterialPass::SB_DestColor: return GL_DST_COLOR;
153  case StdMeshMaterialPass::SB_SrcColor: return GL_SRC_COLOR;
154  case StdMeshMaterialPass::SB_OneMinusDestColor: return GL_ONE_MINUS_DST_COLOR;
155  case StdMeshMaterialPass::SB_OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR;
156  case StdMeshMaterialPass::SB_DestAlpha: return GL_DST_ALPHA;
157  case StdMeshMaterialPass::SB_SrcAlpha: return GL_SRC_ALPHA;
158  case StdMeshMaterialPass::SB_OneMinusDestAlpha: return GL_ONE_MINUS_DST_ALPHA;
159  case StdMeshMaterialPass::SB_OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA;
160  default: assert(false); return GL_ZERO;
161  }
162  }
163 
164  StdStrBuf GetVertexShaderCodeForPass(const StdMeshMaterialPass& pass, bool LowMaxVertexUniformCount)
165  {
166  StdStrBuf buf;
167 
168  if (!::GraphicsResource.Files.LoadEntryString("MeshVertexShader.glsl", &buf))
169  {
170  // Fall back just in case
171  buf.Copy(
172  "attribute vec3 oc_Position;\n"
173  "attribute vec3 oc_Normal;\n"
174  "attribute vec2 oc_TexCoord;\n"
175  "varying vec3 vtxNormal;\n"
176  "varying vec2 texcoord;\n"
177  "uniform mat4 projectionMatrix;\n"
178  "uniform mat4 modelviewMatrix;\n"
179  "uniform mat3 normalMatrix;\n"
180  "\n"
181  "slice(position)\n"
182  "{\n"
183  " gl_Position = projectionMatrix * modelviewMatrix * vec4(oc_Position, 1.0);\n"
184  "}\n"
185  "\n"
186  "slice(texcoord)\n"
187  "{\n"
188  " texcoord = oc_TexCoord;\n"
189  "}\n"
190  "\n"
191  "slice(normal)\n"
192  "{\n"
193  " vtxNormal = normalize(normalMatrix * oc_Normal);\n"
194  "}\n"
195  );
196  }
197 
198  if (pGL->Workarounds.ForceSoftwareTransform)
199  buf.Take(StdStrBuf("#define OC_WA_FORCE_SOFTWARE_TRANSFORM\n") + buf);
200 
201  if (LowMaxVertexUniformCount)
202  return StdStrBuf("#define OC_WA_LOW_MAX_VERTEX_UNIFORM_COMPONENTS\n") + buf;
203  else
204  return buf;
205  }
206 
207  // Note this only gets the code which inserts the slices specific for the pass
208  // -- other slices are independent from this!
209  StdStrBuf GetFragmentShaderCodeForPass(const StdMeshMaterialPass& pass, StdMeshMaterialShaderParameters& params)
210  {
211  StdStrBuf buf;
212 
213  // Produce the fragment shader... first we create one code fragment for each
214  // texture unit, and we count the number of active textures, i.e. texture
215  // units that actually use a texture.
216  unsigned int texIndex = 0;
217  StdStrBuf textureUnitCode(""), textureUnitDeclCode("");
218  for(const auto & texunit : pass.TextureUnits)
219  {
220  textureUnitCode.Append(TextureUnitToCode(texIndex, texunit));
221 
222  if(texunit.HasTexture())
223  {
224  textureUnitDeclCode.Append(FormatString("uniform sampler2D oc_Texture%u;\n", texIndex).getData());
225  params.AddParameter(FormatString("oc_Texture%u", texIndex).getData(), StdMeshMaterialShaderParameter::INT).GetInt() = texIndex;
226 
227  // If the texture unit has texture coordinate transformations,
228  // add a corresponding texture matrix uniform.
229  if(texunit.HasTexCoordAnimation())
230  {
231  textureUnitDeclCode.Append(FormatString("uniform mat4 oc_TextureMatrix%u;\n", texIndex).getData());
232  params.AddParameter(FormatString("oc_TextureMatrix%u", texIndex).getData(), StdMeshMaterialShaderParameter::AUTO_TEXTURE_MATRIX).GetInt() = texIndex;
233  }
234 
235  ++texIndex;
236  }
237  }
238 
239  return FormatString(
240  "%s\n" // Texture units with active textures, only if >0 texture units
241  "uniform vec3 oc_PlayerColor;\n" // This needs to be in-sync with the naming in StdMeshMaterialProgram::CompileShader()
242  "\n"
243  "slice(texture)\n"
244  "{\n"
245  " vec4 diffuse = fragColor;\n"
246  " vec4 currentColor = diffuse;\n"
247  " %s\n"
248  " fragColor = currentColor;\n"
249  "}\n"
250  "\n"
251  "slice(finish)\n"
252  "{\n"
253  " %s\n"
254  "}\n",
255  textureUnitDeclCode.getData(),
256  textureUnitCode.getData(),
257  AlphaTestToCode(pass).getData()
258  );
259  }
260 
261  StdStrBuf GetSHA1HexDigest(const char* text, std::size_t len)
262  {
263  sha1 ctx;
264  ctx.process_bytes(text, len);
265  unsigned int digest[5];
266  ctx.get_digest(digest);
267 
268  return FormatString("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]);
269  }
270 } // anonymous namespace
271 
273 {
274  // TODO: If a technique is not available, show an error message what the problem is
275 
276  // select context, if not already done
277  if (!pCurrCtx) return false;
278 
279  for (unsigned int i = 0; i < mat.Techniques.size(); ++i)
280  {
281  StdMeshMaterialTechnique& technique = mat.Techniques[i];
282  technique.Available = true;
283  for (unsigned int j = 0; j < technique.Passes.size(); ++j)
284  {
285  StdMeshMaterialPass& pass = technique.Passes[j];
286 
287  GLint max_texture_units;
288  glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_units);
289 
290  // OpenGL 3.x guarantees at least 16 TIUs. If the above returns
291  // less it's probably a driver bug. So just keep going.
292  assert(max_texture_units >= 16);
293  max_texture_units = std::min<GLint>(max_texture_units, 16);
294 
295  unsigned int active_texture_units = 0;
296  for(auto & TextureUnit : pass.TextureUnits)
297  if(TextureUnit.HasTexture())
298  ++active_texture_units;
299 
300  if (active_texture_units > static_cast<unsigned int>(max_texture_units))
301  technique.Available = false;
302 
303  for (auto & texunit : pass.TextureUnits)
304  {
305  for (unsigned int l = 0; l < texunit.GetNumTextures(); ++l)
306  {
307  const C4TexRef& texture = texunit.GetTexture(l);
308  glBindTexture(GL_TEXTURE_2D, texture.texName);
309  switch (texunit.TexAddressMode)
310  {
312  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
313  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
314  break;
316  glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, texunit.TexBorderColor);
317  // fallthrough
319  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
320  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
321  break;
323  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
324  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
325  break;
326  }
327 
328  switch (texunit.Filtering[0]) // min
329  {
331  technique.Available = false;
332  break;
334  switch (texunit.Filtering[2]) // mip
335  {
337  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
338  break;
340  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
341  break;
343  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
344  break;
346  technique.Available = false; // invalid
347  break;
348  }
349  break;
351  switch (texunit.Filtering[2]) // mip
352  {
354  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
355  break;
357  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
358  break;
360  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
361  break;
363  technique.Available = false; // invalid
364  break;
365  }
366  break;
368  // unsupported
369  technique.Available = false;
370  break;
371  }
372 
373  switch (texunit.Filtering[1]) // max
374  {
376  technique.Available = false; // invalid
377  break;
379  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
380  break;
382  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
383  break;
385  // unsupported
386  technique.Available = false;
387  break;
388  }
389  } // loop over textures
390  } // loop over texture units
391 
392  // Create fragment and/or vertex shader
393  // if a custom shader is not provided.
394  // Re-use existing programs if the generated
395  // code is the same (determined by SHA1 hash).
396  bool custom_shader = true;
397  if(!pass.VertexShader.Shader)
398  {
399  StdStrBuf buf = GetVertexShaderCodeForPass(pass, Workarounds.LowMaxVertexUniformCount);
400  StdStrBuf hash = GetSHA1HexDigest(buf.getData(), buf.getLength());
401  pass.VertexShader.Shader = mat_manager.AddShader("auto-generated vertex shader", hash.getData(), "glsl", SMMS_VERTEX, buf.getData(), StdMeshMatManager::SMM_AcceptExisting);
402  custom_shader = false;
403  }
404 
405  if(!pass.FragmentShader.Shader)
406  {
407  // TODO: Should use shared_params once we introduce them
408  StdStrBuf buf = GetFragmentShaderCodeForPass(pass, pass.FragmentShader.Parameters);
409  StdStrBuf hash = GetSHA1HexDigest(buf.getData(), buf.getLength());
410  pass.FragmentShader.Shader = mat_manager.AddShader("auto-generated fragment shader", hash.getData(), "glsl", SMMS_FRAGMENT, buf.getData(), StdMeshMatManager::SMM_AcceptExisting);
411  }
412 
413  // Then, link the program, and resolve parameter locations
414  StdStrBuf name(FormatString("%s:%s:%s", mat.Name.getData(), technique.Name.getData(), pass.Name.getData()));
415  const StdMeshMaterialProgram* added_program = mat_manager.AddProgram(name.getData(), loader, pass.FragmentShader, pass.VertexShader, pass.GeometryShader);
416  if(!added_program)
417  {
418  // If the program could not be compiled, try again with the LowMaxVertexUniformCount workaround.
419  // See bug #1368.
420  if (!custom_shader && !Workarounds.LowMaxVertexUniformCount)
421  {
422  StdStrBuf buf = GetVertexShaderCodeForPass(pass, true);
423  StdStrBuf hash = GetSHA1HexDigest(buf.getData(), buf.getLength());
424  pass.VertexShader.Shader = mat_manager.AddShader("auto-generated vertex shader", hash.getData(), "glsl", SMMS_VERTEX, buf.getData(), StdMeshMatManager::SMM_AcceptExisting);
425 
426  added_program = mat_manager.AddProgram(name.getData(), loader, pass.FragmentShader, pass.VertexShader, pass.GeometryShader);
427  if(added_program)
428  {
429  // If this actually work, cache the result, so we don't
430  // need to fail again next time before trying the workaround.
431  Workarounds.LowMaxVertexUniformCount = true;
432  Log(" gl: Enabling low max vertex uniform workaround");
433  }
434  }
435  }
436 
437  if (!added_program)
438  {
439  technique.Available = false;
440  }
441  else
442  {
443  std::unique_ptr<StdMeshMaterialPass::ProgramInstance> program_instance(new StdMeshMaterialPass::ProgramInstance(added_program, &pass.FragmentShader, &pass.VertexShader, &pass.GeometryShader));
444  pass.Program = std::move(program_instance);
445  }
446  }
447 
448  if (technique.Available && mat.BestTechniqueIndex == -1)
449  mat.BestTechniqueIndex = i;
450  }
451 
452  return mat.BestTechniqueIndex != -1;
453 }
454 
455 // TODO: We should add a class, C4MeshRenderer, which contains all the functions
456 // in this namespace, and avoids passing around so many parameters.
457 namespace
458 {
459  // Apply Zoom and Transformation to the current matrix stack. Return
460  // parity of the transformation.
461  bool ApplyZoomAndTransform(float ZoomX, float ZoomY, float Zoom, C4BltTransform* pTransform, StdProjectionMatrix& projection)
462  {
463  // Apply zoom
464  Translate(projection, ZoomX, ZoomY, 0.0f);
465  Scale(projection, Zoom, Zoom, 1.0f);
466  Translate(projection, -ZoomX, -ZoomY, 0.0f);
467 
468  // Apply transformation
469  if (pTransform)
470  {
471  StdProjectionMatrix transform;
472  transform(0, 0) = pTransform->mat[0];
473  transform(0, 1) = pTransform->mat[1];
474  transform(0, 2) = 0.0f;
475  transform(0, 3) = pTransform->mat[2];
476  transform(1, 0) = pTransform->mat[3];
477  transform(1, 1) = pTransform->mat[4];
478  transform(1, 2) = 0.0f;
479  transform(1, 3) = pTransform->mat[5];
480  transform(2, 0) = 0.0f;
481  transform(2, 1) = 0.0f;
482  transform(2, 2) = 1.0f;
483  transform(2, 3) = 0.0f;
484  transform(3, 0) = pTransform->mat[6];
485  transform(3, 1) = pTransform->mat[7];
486  transform(3, 2) = 0.0f;
487  transform(3, 3) = pTransform->mat[8];
488  projection *= transform;
489 
490  // Compute parity of the transformation matrix - if parity is swapped then
491  // we need to cull front faces instead of back faces.
492  const float det = transform(0,0)*transform(1,1)*transform(3,3)
493  + transform(1,0)*transform(3,1)*transform(0,3)
494  + transform(3,0)*transform(0,1)*transform(1,3)
495  - transform(0,0)*transform(3,1)*transform(1,3)
496  - transform(1,0)*transform(0,1)*transform(3,3)
497  - transform(3,0)*transform(1,1)*transform(0,3);
498 
499  return det > 0;
500  }
501 
502  return true;
503  }
504 
505  void SetStandardUniforms(C4ShaderCall& call, DWORD dwModClr, DWORD dwPlayerColor, DWORD dwBlitMode, bool cullFace, const C4FoWRegion* pFoW, const C4Rect& clipRect, const C4Rect& outRect)
506  {
507  // Draw transform
508  const float fMod[4] = {
509  ((dwModClr >> 16) & 0xff) / 255.0f,
510  ((dwModClr >> 8) & 0xff) / 255.0f,
511  ((dwModClr ) & 0xff) / 255.0f,
512  ((dwModClr >> 24) & 0xff) / 255.0f
513  };
514  call.SetUniform4fv(C4SSU_ClrMod, 1, fMod);
516  call.SetUniform2f(C4SSU_Resolution, outRect.Wdt, outRect.Hgt);
517 
518  // Player color
519  const float fPlrClr[3] = {
520  ((dwPlayerColor >> 16) & 0xff) / 255.0f,
521  ((dwPlayerColor >> 8) & 0xff) / 255.0f,
522  ((dwPlayerColor ) & 0xff) / 255.0f,
523  };
524  call.SetUniform3fv(C4SSU_OverlayClr, 1, fPlrClr);
525 
526  // Backface culling flag
527  call.SetUniform1f(C4SSU_CullMode, cullFace ? 0.0f : 1.0f);
528 
529  // Dynamic light
530  if(pFoW != nullptr)
531  {
533  glBindTexture(GL_TEXTURE_2D, pFoW->getSurfaceName());
534  float lightTransform[6];
535  pFoW->GetFragTransform(clipRect, outRect, lightTransform);
536  call.SetUniformMatrix2x3fv(C4SSU_LightTransform, 1, lightTransform);
537 
539  glBindTexture(GL_TEXTURE_2D, pFoW->getFoW()->Ambient.Tex);
541  float ambientTransform[6];
542  pFoW->getFoW()->Ambient.GetFragTransform(pFoW->getViewportRegion(), clipRect, outRect, ambientTransform);
543  call.SetUniformMatrix2x3fv(C4SSU_AmbientTransform, 1, ambientTransform);
544  }
545 
546  // Current frame counter
548  }
549 
550  bool ResolveAutoParameter(C4ShaderCall& call, StdMeshMaterialShaderParameter& parameter, StdMeshMaterialShaderParameter::Auto value, DWORD dwModClr, DWORD dwPlayerColor, DWORD dwBlitMode, const C4FoWRegion* pFoW, const C4Rect& clipRect)
551  {
552  // There are no auto parameters implemented yet
553  assert(false);
554  return false;
555  }
556 
557  StdProjectionMatrix ResolveAutoTextureMatrix(const StdSubMeshInstance& instance, const StdMeshMaterialTechnique& technique, unsigned int passIndex, unsigned int texUnitIndex)
558  {
559  assert(passIndex < technique.Passes.size());
560  const StdMeshMaterialPass& pass = technique.Passes[passIndex];
561 
562  assert(texUnitIndex < pass.TextureUnits.size());
563  const StdMeshMaterialTextureUnit& texunit = pass.TextureUnits[texUnitIndex];
564 
566  const double Position = instance.GetTexturePosition(passIndex, texUnitIndex);
567 
568  for (const auto & trans : texunit.Transformations)
569  {
570  StdProjectionMatrix temp_matrix;
571  switch (trans.TransformType)
572  {
574  Translate(matrix, trans.Scroll.X, trans.Scroll.Y, 0.0f);
575  break;
577  Translate(matrix, trans.GetScrollX(Position), trans.GetScrollY(Position), 0.0f);
578  break;
580  Rotate(matrix, trans.Rotate.Angle, 0.0f, 0.0f, 1.0f);
581  break;
583  Rotate(matrix, trans.GetRotate(Position), 0.0f, 0.0f, 1.0f);
584  break;
586  Scale(matrix, trans.Scale.X, trans.Scale.Y, 1.0f);
587  break;
589  for (int i = 0; i < 16; ++i)
590  temp_matrix(i / 4, i % 4) = trans.Transform.M[i];
591  matrix *= temp_matrix;
592  break;
594  switch (trans.WaveXForm.XForm)
595  {
597  Translate(matrix, trans.GetWaveXForm(Position), 0.0f, 0.0f);
598  break;
600  Translate(matrix, 0.0f, trans.GetWaveXForm(Position), 0.0f);
601  break;
603  Rotate(matrix, trans.GetWaveXForm(Position), 0.0f, 0.0f, 1.0f);
604  break;
606  Scale(matrix, trans.GetWaveXForm(Position), 1.0f, 1.0f);
607  break;
609  Scale(matrix, 1.0f, trans.GetWaveXForm(Position), 1.0f);
610  break;
611  }
612  break;
613  }
614  }
615 
616  return matrix;
617  }
618 
619  struct BoneTransform
620  {
621  float m[3][4];
622  };
623 
624  std::vector<BoneTransform> CookBoneTransforms(const StdMeshInstance& mesh_instance)
625  {
626  // Cook the bone transform matrixes into something that OpenGL can use. This could be moved into RenderMeshImpl.
627  // Or, even better, we could upload them into a UBO, but Intel doesn't support them prior to Sandy Bridge.
628 
629  std::vector<BoneTransform> bones;
630  if (mesh_instance.GetBoneCount() == 0)
631  {
632 #pragma clang diagnostic ignored "-Wmissing-braces"
633  // Upload dummy bone so we don't have to do branching in the vertex shader
634  static const BoneTransform dummy_bone = {
635  1.0f, 0.0f, 0.0f, 0.0f,
636  0.0f, 1.0f, 0.0f, 0.0f,
637  0.0f, 0.0f, 1.0f, 0.0f
638  };
639  bones.push_back(dummy_bone);
640  }
641  else
642  {
643  bones.reserve(mesh_instance.GetBoneCount());
644  for (size_t bone_index = 0; bone_index < mesh_instance.GetBoneCount(); ++bone_index)
645  {
646  const StdMeshMatrix &bone = mesh_instance.GetBoneTransform(bone_index);
647  BoneTransform cooked_bone = {
648  bone(0, 0), bone(0, 1), bone(0, 2), bone(0, 3),
649  bone(1, 0), bone(1, 1), bone(1, 2), bone(1, 3),
650  bone(2, 0), bone(2, 1), bone(2, 2), bone(2, 3)
651  };
652  bones.push_back(cooked_bone);
653  }
654  }
655  return bones;
656  }
657 
658  struct PretransformedMeshVertex
659  {
660  float nx, ny, nz;
661  float x, y, z;
662  };
663 
664  void PretransformMeshVertex(PretransformedMeshVertex *out, const StdMeshVertex &in, const StdMeshInstance &mesh_instance)
665  {
666  // If the first bone assignment has a weight of 0, all others are zero
667  // as well, or the loader would have overwritten the assignment
668  if (in.bone_weight[0] == 0.0f || mesh_instance.GetBoneCount() == 0)
669  {
670  out->x = in.x;
671  out->y = in.y;
672  out->z = in.z;
673  out->nx = in.nx;
674  out->ny = in.ny;
675  out->nz = in.nz;
676  }
677  else
678  {
679  PretransformedMeshVertex vtx{ 0, 0, 0, 0, 0, 0 };
680  for (size_t i = 0; i < StdMeshVertex::MaxBoneWeightCount && in.bone_weight[i] > 0; ++i)
681  {
682  float weight = in.bone_weight[i];
683  const auto &bone = mesh_instance.GetBoneTransform(in.bone_index[i]);
684  auto vertex = weight * (bone * in);
685  vtx.nx += vertex.nx;
686  vtx.ny += vertex.ny;
687  vtx.nz += vertex.nz;
688  vtx.x += vertex.x;
689  vtx.y += vertex.y;
690  vtx.z += vertex.z;
691  }
692  *out = vtx;
693  }
694  }
695 
696  void PretransformMeshVertices(const StdMeshInstance &mesh_instance, const StdSubMeshInstance& instance, GLuint vbo)
697  {
698  assert(pGL->Workarounds.ForceSoftwareTransform);
699  glBindBuffer(GL_ARRAY_BUFFER, vbo);
700 
701  const auto &original_vertices = mesh_instance.GetSharedVertices().empty() ? instance.GetSubMesh().GetVertices() : mesh_instance.GetSharedVertices();
702  const size_t vertex_count = original_vertices.size();
703 
704  // Unmapping the buffer may fail for certain reasons, in which case we need to try again.
705  do
706  {
707  glBufferData(GL_ARRAY_BUFFER, vertex_count * sizeof(PretransformedMeshVertex), nullptr, GL_STREAM_DRAW);
708  void *map = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
709  PretransformedMeshVertex *buffer = new (map) PretransformedMeshVertex[vertex_count];
710 
711  for (size_t i = 0; i < vertex_count; ++i)
712  {
713  PretransformMeshVertex(&buffer[i], original_vertices[i], mesh_instance);
714  }
715  } while (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE);
716  // Unbind the buffer so following rendering calls do not use it
717  glBindBuffer(GL_ARRAY_BUFFER, 0);
718  }
719 
720  void RenderSubMeshImpl(const StdProjectionMatrix& projectionMatrix, const StdMeshMatrix& modelviewMatrix, const StdMeshInstance& mesh_instance, const StdSubMeshInstance& instance, DWORD dwModClr, DWORD dwBlitMode, DWORD dwPlayerColor, const C4FoWRegion* pFoW, const C4Rect& clipRect, const C4Rect& outRect, bool parity)
721  {
722  // Don't render with degenerate matrix
723  if (fabs(modelviewMatrix.Determinant()) < 1e-6)
724  return;
725 
726  const StdMeshMaterial& material = instance.GetMaterial();
727  assert(material.BestTechniqueIndex != -1);
728  const StdMeshMaterialTechnique& technique = material.Techniques[material.BestTechniqueIndex];
729 
730  bool using_shared_vertices = instance.GetSubMesh().GetVertices().empty();
731  GLuint vbo = mesh_instance.GetMesh().GetVBO();
732  GLuint ibo = mesh_instance.GetIBO();
733  unsigned int vaoid = mesh_instance.GetVAOID();
734  size_t vertex_buffer_offset = using_shared_vertices ? 0 : instance.GetSubMesh().GetOffsetInVBO();
735  size_t index_buffer_offset = instance.GetSubMesh().GetOffsetInIBO(); // note this is constant
736 
737  const bool ForceSoftwareTransform = pGL->Workarounds.ForceSoftwareTransform;
738  GLuint pretransform_vbo;
739 
740  std::vector<BoneTransform> bones;
741  if (!ForceSoftwareTransform)
742  {
743  bones = CookBoneTransforms(mesh_instance);
744  }
745  else
746  {
747  glGenBuffers(1, &pretransform_vbo);
748  PretransformMeshVertices(mesh_instance, instance, pretransform_vbo);
749  }
750  // Modelview matrix does not change between passes, so cache it here
751  const StdMeshMatrix normalMatrixTranspose = StdMeshMatrix::Inverse(modelviewMatrix);
752 
753  // Render each pass
754  for (unsigned int i = 0; i < technique.Passes.size(); ++i)
755  {
756  const StdMeshMaterialPass& pass = technique.Passes[i];
757 
758  if (!pass.DepthCheck)
759  glDisable(GL_DEPTH_TEST);
760 
761  glDepthMask(pass.DepthWrite ? GL_TRUE : GL_FALSE);
762 
763  if (pass.AlphaToCoverage)
764  glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
765  else
766  glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
767 
768  glFrontFace(parity ? GL_CW : GL_CCW);
769  if (mesh_instance.GetCompletion() < 1.0f)
770  {
771  // Backfaces might be visible when completion is < 1.0f since front
772  // faces might be omitted.
773  glDisable(GL_CULL_FACE);
774  }
775  else
776  {
777  switch (pass.CullHardware)
778  {
780  glEnable(GL_CULL_FACE);
781  glCullFace(GL_BACK);
782  break;
784  glEnable(GL_CULL_FACE);
785  glCullFace(GL_FRONT);
786  break;
788  glDisable(GL_CULL_FACE);
789  break;
790  }
791  }
792 
793  // Overwrite blend mode with default alpha blending when alpha in clrmod
794  // is <255. This makes sure that normal non-blended meshes can have
795  // blending disabled in their material script (which disables expensive
796  // face ordering) but when they are made translucent via clrmod
797  if (!(dwBlitMode & C4GFXBLIT_ADDITIVE))
798  {
799  if (((dwModClr >> 24) & 0xff) < 0xff) // && (!(dwBlitMode & C4GFXBLIT_MOD2)) )
800  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
801  else
802  glBlendFunc(OgreBlendTypeToGL(pass.SceneBlendFactors[0]),
803  OgreBlendTypeToGL(pass.SceneBlendFactors[1]));
804  }
805  else
806  {
807  if (((dwModClr >> 24) & 0xff) < 0xff) // && (!(dwBlitMode & C4GFXBLIT_MOD2)) )
808  glBlendFunc(GL_SRC_ALPHA, GL_ONE);
809  else
810  glBlendFunc(OgreBlendTypeToGL(pass.SceneBlendFactors[0]), GL_ONE);
811  }
812 
813  assert(pass.Program.get() != nullptr);
814 
815  // Upload all parameters to the shader
816  int ssc = 0;
817  if (dwBlitMode & C4GFXBLIT_MOD2) ssc |= C4SSC_MOD2;
818  if (pFoW != nullptr) ssc |= C4SSC_LIGHT;
819  const C4Shader* shader = pass.Program->Program->GetShader(ssc);
820  if (!shader) return;
821  C4ShaderCall call(shader);
822  call.Start();
823 
824  // Upload projection, modelview and normal matrices
825  call.SetUniformMatrix4x4(C4SSU_ProjectionMatrix, projectionMatrix);
826  call.SetUniformMatrix4x4(C4SSU_ModelViewMatrix, modelviewMatrix);
827  call.SetUniformMatrix3x3Transpose(C4SSU_NormalMatrix, normalMatrixTranspose);
828 
829 
830  // Upload material properties
836 
837  // Upload the current bone transformation matrixes (if there are any)
838  if (!ForceSoftwareTransform)
839  {
840  if (!bones.empty())
841  {
842  if (pGL->Workarounds.LowMaxVertexUniformCount)
843  glUniformMatrix3x4fv(shader->GetUniform(C4SSU_Bones), bones.size(), GL_FALSE, &bones[0].m[0][0]);
844  else
845  glUniformMatrix4x3fv(shader->GetUniform(C4SSU_Bones), bones.size(), GL_TRUE, &bones[0].m[0][0]);
846  }
847  }
848 
849  GLuint vao;
850  const bool has_vao = pGL->GetVAO(vaoid, vao);
851  glBindVertexArray(vao);
852  if (!has_vao)
853  {
854  // Bind the vertex data of the mesh
855  // Note this relies on the fact that all vertex
856  // attributes for all shaders are at the same
857  // locations.
858  // TODO: And this fails if the mesh changes
859  // from a material with texture to one without
860  // or vice versa.
861  glBindBuffer(GL_ARRAY_BUFFER, vbo);
862  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
863 #define VERTEX_OFFSET(field) reinterpret_cast<const uint8_t *>(offsetof(StdMeshVertex, field))
864  if (shader->GetAttribute(C4SSA_TexCoord) != -1)
865  glVertexAttribPointer(shader->GetAttribute(C4SSA_TexCoord), 2, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(u));
866  if (!ForceSoftwareTransform)
867  {
868  glVertexAttribPointer(shader->GetAttribute(C4SSA_Position), 3, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(x));
869  glVertexAttribPointer(shader->GetAttribute(C4SSA_Normal), 3, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(nx));
870  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_Position));
871  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_Normal));
872 
873  glVertexAttribPointer(shader->GetAttribute(C4SSA_BoneWeights0), 4, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(bone_weight));
874  glVertexAttribPointer(shader->GetAttribute(C4SSA_BoneWeights1), 4, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(bone_weight) + 4 * sizeof(std::remove_all_extents<decltype(StdMeshVertex::bone_weight)>::type));
875  glVertexAttribPointer(shader->GetAttribute(C4SSA_BoneIndices0), 4, GL_SHORT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(bone_index));
876  glVertexAttribPointer(shader->GetAttribute(C4SSA_BoneIndices1), 4, GL_SHORT, GL_FALSE, sizeof(StdMeshVertex), VERTEX_OFFSET(bone_index) + 4 * sizeof(std::remove_all_extents<decltype(StdMeshVertex::bone_index)>::type));
877  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_BoneWeights0));
878  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_BoneWeights1));
879  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_BoneIndices0));
880  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_BoneIndices1));
881  }
882  if (shader->GetAttribute(C4SSA_TexCoord) != -1)
883  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_TexCoord));
884 
885 #undef VERTEX_OFFSET
886  }
887 
888  if (ForceSoftwareTransform)
889  {
890  glBindBuffer(GL_ARRAY_BUFFER, pretransform_vbo);
891 #define VERTEX_OFFSET(field) reinterpret_cast<const uint8_t *>(offsetof(PretransformedMeshVertex, field))
892  glVertexAttribPointer(shader->GetAttribute(C4SSA_Position), 3, GL_FLOAT, GL_FALSE, sizeof(PretransformedMeshVertex), VERTEX_OFFSET(x));
893  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_Position));
894  glVertexAttribPointer(shader->GetAttribute(C4SSA_Normal), 3, GL_FLOAT, GL_FALSE, sizeof(PretransformedMeshVertex), VERTEX_OFFSET(nx));
895  glEnableVertexAttribArray(shader->GetAttribute(C4SSA_Normal));
896 #undef VERTEX_OFFSET
897  glBindBuffer(GL_ARRAY_BUFFER, 0);
898  }
899 
900  // Bind textures
901  for (unsigned int j = 0; j < pass.TextureUnits.size(); ++j)
902  {
903  const StdMeshMaterialTextureUnit& texunit = pass.TextureUnits[j];
904  if (texunit.HasTexture())
905  {
906  call.AllocTexUnit(-1);
907  const unsigned int Phase = instance.GetTexturePhase(i, j);
908  glBindTexture(GL_TEXTURE_2D, texunit.GetTexture(Phase).texName);
909  }
910  }
911 
912  // Set uniforms and instance parameters
913  SetStandardUniforms(call, dwModClr, dwPlayerColor, dwBlitMode, pass.CullHardware != StdMeshMaterialPass::CH_None, pFoW, clipRect, outRect);
914  for(auto & Parameter : pass.Program->Parameters)
915  {
916  const int uniform = Parameter.UniformIndex;
917  if(!shader->HaveUniform(uniform)) continue; // optimized out
918 
919  const StdMeshMaterialShaderParameter* parameter = Parameter.Parameter;
920 
921  StdMeshMaterialShaderParameter auto_resolved;
923  {
924  if(!ResolveAutoParameter(call, auto_resolved, parameter->GetAuto(), dwModClr, dwPlayerColor, dwBlitMode, pFoW, clipRect))
925  continue;
926  parameter = &auto_resolved;
927  }
928 
929  switch(parameter->GetType())
930  {
932  call.SetUniformMatrix4x4(uniform, ResolveAutoTextureMatrix(instance, technique, i, parameter->GetInt()));
933  break;
935  call.SetUniform1i(uniform, parameter->GetInt());
936  break;
938  call.SetUniform1f(uniform, parameter->GetFloat());
939  break;
941  call.SetUniform2fv(uniform, 1, parameter->GetFloatv());
942  break;
944  call.SetUniform3fv(uniform, 1, parameter->GetFloatv());
945  break;
947  call.SetUniform4fv(uniform, 1, parameter->GetFloatv());
948  break;
950  call.SetUniformMatrix4x4fv(uniform, 1, parameter->GetMatrix());
951  break;
952  default:
953  assert(false);
954  break;
955  }
956  }
957 
958  pDraw->scriptUniform.Apply(call);
959 
960  size_t vertex_count = 3 * instance.GetNumFaces();
961  assert (vertex_buffer_offset % sizeof(StdMeshVertex) == 0);
962  size_t base_vertex = vertex_buffer_offset / sizeof(StdMeshVertex);
963  glDrawElementsBaseVertex(GL_TRIANGLES, vertex_count, GL_UNSIGNED_INT, reinterpret_cast<void*>(index_buffer_offset), base_vertex);
964  glBindVertexArray(0);
965  call.Finish();
966 
967  if(!pass.DepthCheck)
968  glEnable(GL_DEPTH_TEST);
969  }
970 
971  if (ForceSoftwareTransform)
972  glDeleteBuffers(1, &pretransform_vbo);
973  }
974 
975  void RenderMeshImpl(const StdProjectionMatrix& projectionMatrix, const StdMeshMatrix& modelviewMatrix, StdMeshInstance& instance, DWORD dwModClr, DWORD dwBlitMode, DWORD dwPlayerColor, const C4FoWRegion* pFoW, const C4Rect& clipRect, const C4Rect& outRect, bool parity); // Needed by RenderAttachedMesh
976 
977  void RenderAttachedMesh(const StdProjectionMatrix& projectionMatrix, const StdMeshMatrix& modelviewMatrix, StdMeshInstance::AttachedMesh* attach, DWORD dwModClr, DWORD dwBlitMode, DWORD dwPlayerColor, const C4FoWRegion* pFoW, const C4Rect& clipRect, const C4Rect& outRect, bool parity)
978  {
979  const StdMeshMatrix& FinalTrans = attach->GetFinalTransformation();
980 
981  // Take the player color from the C4Object, if the attached object is not a definition
982  // This is a bit unfortunate because it requires access to C4Object which is otherwise
983  // avoided in this code. It could be replaced by virtual function calls to StdMeshDenumerator
984  C4MeshDenumerator* denumerator = dynamic_cast<C4MeshDenumerator*>(attach->ChildDenumerator);
985  if(denumerator && denumerator->GetObject())
986  {
987  dwModClr = denumerator->GetObject()->ColorMod;
988  dwBlitMode = denumerator->GetObject()->BlitMode;
989  dwPlayerColor = denumerator->GetObject()->Color;
990  }
991 
992  // TODO: Take attach transform's parity into account
993  StdMeshMatrix newModelviewMatrix = modelviewMatrix * FinalTrans;
994  RenderMeshImpl(projectionMatrix, newModelviewMatrix, *attach->Child, dwModClr, dwBlitMode, dwPlayerColor, pFoW, clipRect, outRect, parity);
995  }
996 
997  void RenderMeshImpl(const StdProjectionMatrix& projectionMatrix, const StdMeshMatrix& modelviewMatrix, StdMeshInstance& instance, DWORD dwModClr, DWORD dwBlitMode, DWORD dwPlayerColor, const C4FoWRegion* pFoW, const C4Rect& clipRect, const C4Rect& outRect, bool parity)
998  {
999  const StdMesh& mesh = instance.GetMesh();
1000 
1001  // Render AM_DrawBefore attached meshes
1002  StdMeshInstance::AttachedMeshIter attach_iter = instance.AttachedMeshesBegin();
1003 
1004  for (; attach_iter != instance.AttachedMeshesEnd() && ((*attach_iter)->GetFlags() & StdMeshInstance::AM_DrawBefore); ++attach_iter)
1005  RenderAttachedMesh(projectionMatrix, modelviewMatrix, *attach_iter, dwModClr, dwBlitMode, dwPlayerColor, pFoW, clipRect, outRect, parity);
1006 
1007  // Check if we should draw in wireframe or normal mode
1008  if(dwBlitMode & C4GFXBLIT_WIREFRAME)
1009  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1010 
1011  // Render each submesh
1012  for (unsigned int i = 0; i < mesh.GetNumSubMeshes(); ++i)
1013  RenderSubMeshImpl(projectionMatrix, modelviewMatrix, instance, instance.GetSubMeshOrdered(i), dwModClr, dwBlitMode, dwPlayerColor, pFoW, clipRect, outRect, parity);
1014 
1015  // reset old mode to prevent rendering errors
1016  if(dwBlitMode & C4GFXBLIT_WIREFRAME)
1017  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1018 
1019  // Render non-AM_DrawBefore attached meshes
1020  for (; attach_iter != instance.AttachedMeshesEnd(); ++attach_iter)
1021  RenderAttachedMesh(projectionMatrix, modelviewMatrix, *attach_iter, dwModClr, dwBlitMode, dwPlayerColor, pFoW, clipRect, outRect, parity);
1022  }
1023 }
1024 
1025 void CStdGL::PerformMesh(StdMeshInstance &instance, float tx, float ty, float twdt, float thgt, DWORD dwPlayerColor, C4BltTransform* pTransform)
1026 {
1027  // Field of View for perspective projection, in degrees
1028  static const float FOV = 60.0f;
1029  static const float TAN_FOV = tan(FOV / 2.0f / 180.0f * M_PI);
1030 
1031  // Check mesh transformation; abort when it is degenerate.
1032  bool mesh_transform_parity = false;
1033  if (MeshTransform)
1034  {
1035  const float det = MeshTransform->Determinant();
1036  if (fabs(det) < 1e-6)
1037  return;
1038  else if (det < 0.0f)
1039  mesh_transform_parity = true;
1040  }
1041 
1042  const StdMesh& mesh = instance.GetMesh();
1043 
1044  bool parity = false;
1045 
1046  // Convert bounding box to clonk coordinate system
1047  // (TODO: We should cache this, not sure where though)
1048  const StdMeshBox& box = mesh.GetBoundingBox();
1049  StdMeshVector v1, v2;
1050  v1.x = box.x1; v1.y = box.y1; v1.z = box.z1;
1051  v2.x = box.x2; v2.y = box.y2; v2.z = box.z2;
1052 
1053  // Vector from origin of mesh to center of mesh
1054  const StdMeshVector MeshCenter = (v1 + v2)/2.0f;
1055 
1056  glEnable(GL_DEPTH_TEST);
1057  glEnable(GL_BLEND); // TODO: Shouldn't this always be enabled? - blending does not work for meshes without this though.
1058 
1059  // TODO: We ignore the additive drawing flag for meshes but instead
1060  // set the blending mode of the corresponding material. I'm not sure
1061  // how the two could be combined.
1062  // TODO: Maybe they can be combined using a pixel shader which does
1063  // ftransform() and then applies colormod, additive and mod2
1064  // on the result (with alpha blending).
1065  //int iAdditive = dwBlitMode & C4GFXBLIT_ADDITIVE;
1066  //glBlendFunc(GL_SRC_ALPHA, iAdditive ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA);
1067 
1068  // Mesh extents
1069  const float b = fabs(v2.x - v1.x)/2.0f;
1070  const float h = fabs(v2.y - v1.y)/2.0f;
1071  const float l = fabs(v2.z - v1.z)/2.0f;
1072 
1073  // Set up projection matrix first. We do transform and Zoom with the
1074  // projection matrix, so that lighting is applied to the untransformed/unzoomed
1075  // mesh.
1076  StdProjectionMatrix projectionMatrix;
1077  if (!fUsePerspective)
1078  {
1079  // Load the orthographic projection
1080  projectionMatrix = ProjectionMatrix;
1081 
1082  if (!ApplyZoomAndTransform(ZoomX, ZoomY, Zoom, pTransform, projectionMatrix))
1083  parity = !parity;
1084 
1085  // Scale so that the mesh fits in (tx,ty,twdt,thgt)
1086  const float rx = -std::min(v1.x,v2.x) / fabs(v2.x - v1.x);
1087  const float ry = -std::min(v1.y,v2.y) / fabs(v2.y - v1.y);
1088  const float dx = tx + rx*twdt;
1089  const float dy = ty + ry*thgt;
1090 
1091  // Scale so that Z coordinate is between -1 and 1, otherwise parts of
1092  // the mesh could be clipped away by the near or far clipping plane.
1093  // Note that this only works for the projection matrix, otherwise
1094  // lighting is screwed up.
1095 
1096  // This technique might also enable us not to clear the depth buffer
1097  // after every mesh rendering - we could simply scale the first mesh
1098  // of the scene so that it's Z coordinate is between 0 and 1, scale
1099  // the second mesh that it is between 1 and 2, and so on.
1100  // This of course requires an orthogonal projection so that the
1101  // meshes don't look distorted - if we should ever decide to use
1102  // a perspective projection we need to think of something different.
1103  // Take also into account that the depth is not linear but linear
1104  // in the logarithm (if I am not mistaken), so goes as 1/z
1105 
1106  // Don't scale by Z extents since mesh might be transformed
1107  // by MeshTransformation, so use GetBoundingRadius to be safe.
1108  // Note this still fails if mesh is scaled in Z direction or
1109  // there are attached meshes.
1110  const float scz = 1.0/(mesh.GetBoundingRadius());
1111 
1112  Translate(projectionMatrix, dx, dy, 0.0f);
1113  Scale(projectionMatrix, 1.0f, 1.0f, scz);
1114  }
1115  else
1116  {
1117  // Perspective projection. This code transforms the projected
1118  // 3D model into the target area.
1119  const C4Rect clipRect = GetClipRect();
1120  projectionMatrix = StdProjectionMatrix::Identity();
1121 
1122  // Back to GL device coordinates
1123  Translate(projectionMatrix, -1.0f, 1.0f, 0.0f);
1124  Scale(projectionMatrix, 2.0f/clipRect.Wdt, -2.0f/clipRect.Hgt, 1.0f);
1125 
1126  Translate(projectionMatrix, -clipRect.x, -clipRect.y, 0.0f);
1127  if (!ApplyZoomAndTransform(ZoomX, ZoomY, Zoom, pTransform, projectionMatrix))
1128  parity = !parity;
1129 
1130  // Move to target location and compensate for 1.0f aspect
1131  float ttx = tx, tty = ty, ttwdt = twdt, tthgt = thgt;
1132  if(twdt > thgt)
1133  {
1134  tty += (thgt-twdt)/2.0;
1135  tthgt = twdt;
1136  }
1137  else
1138  {
1139  ttx += (twdt-thgt)/2.0;
1140  ttwdt = thgt;
1141  }
1142 
1143  Translate(projectionMatrix, ttx, tty, 0.0f);
1144  Scale(projectionMatrix, ((float)ttwdt)/clipRect.Wdt, ((float)tthgt)/clipRect.Hgt, 1.0f);
1145 
1146  // Return to Clonk coordinate frame
1147  Scale(projectionMatrix, clipRect.Wdt/2.0, -clipRect.Hgt/2.0, 1.0f);
1148  Translate(projectionMatrix, 1.0f, -1.0f, 0.0f);
1149 
1150  // Fix for the case when we have different aspect ratios
1151  const float ta = twdt / thgt;
1152  const float ma = b / h;
1153  if(ta <= 1 && ta/ma <= 1)
1154  Scale(projectionMatrix, std::max(ta, ta/ma), std::max(ta, ta/ma), 1.0f);
1155  else if(ta >= 1 && ta/ma >= 1)
1156  Scale(projectionMatrix, std::max(1.0f/ta, ma/ta), std::max(1.0f/ta, ma/ta), 1.0f);
1157 
1158  // Apply perspective projection. After this, x and y range from
1159  // -1 to 1, and this is mapped into tx/ty/twdt/thgt by the above code.
1160  // Aspect is 1.0f which is changed above as well.
1161  Perspective(projectionMatrix, 1.0f/TAN_FOV, 1.0f, 0.1f, 100.0f);
1162  }
1163 
1164  // Now set up the modelview matrix
1165  StdMeshMatrix modelviewMatrix;
1166  if (fUsePerspective)
1167  {
1168  // Setup camera position so that the mesh with uniform transformation
1169  // fits well into a square target (without distortion).
1170  const float EyeR = l + std::max(b/TAN_FOV, h/TAN_FOV);
1171  const StdMeshVector Eye = StdMeshVector::Translate(MeshCenter.x, MeshCenter.y, MeshCenter.z + EyeR);
1172 
1173  // Up vector is unit vector in theta direction
1174  const StdMeshVector Up = StdMeshVector::Translate(0.0f, -1.0f, 0.0f);
1175 
1176  // Fix X axis (???)
1177  modelviewMatrix = StdMeshMatrix::Scale(-1.0f, 1.0f, 1.0f);
1178 
1179  // center on mesh's bounding box, so that the mesh is really in the center of the viewport
1180  modelviewMatrix *= StdMeshMatrix::LookAt(Eye, MeshCenter, Up);
1181  }
1182  else
1183  {
1184  modelviewMatrix = StdMeshMatrix::Identity();
1185  }
1186 
1187  // Apply mesh transformation matrix
1188  if (MeshTransform)
1189  {
1190  // Apply MeshTransformation (in the Mesh's coordinate system)
1191  modelviewMatrix *= *MeshTransform;
1192  // Keep track of parity
1193  if (mesh_transform_parity) parity = !parity;
1194  }
1195 
1196  DWORD dwModClr = BlitModulated ? BlitModulateClr : 0xffffffff;
1197 
1198  const C4Rect clipRect = GetClipRect();
1199  const C4Rect outRect = GetOutRect();
1200 
1201  RenderMeshImpl(projectionMatrix, modelviewMatrix, instance, dwModClr, dwBlitMode, dwPlayerColor, pFoW, clipRect, outRect, parity);
1202 
1203  // Reset state
1204  //glActiveTexture(GL_TEXTURE0);
1205  glDepthMask(GL_TRUE);
1206  glDisable(GL_DEPTH_TEST);
1207  glDisable(GL_CULL_FACE);
1208  glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
1209 
1210  // TODO: glScissor, so that we only clear the area the mesh covered.
1211  glClear(GL_DEPTH_BUFFER_BIT);
1212 }
1213 
1214 #endif // USE_CONSOLE
C4Draw * pDraw
Definition: C4Draw.cpp:42
CStdGL * pGL
Definition: C4DrawGL.cpp:907
@ C4SSA_TexCoord
Definition: C4DrawGL.h:91
@ C4SSA_BoneIndices0
Definition: C4DrawGL.h:94
@ C4SSA_Normal
Definition: C4DrawGL.h:90
@ C4SSA_BoneIndices1
Definition: C4DrawGL.h:95
@ C4SSA_BoneWeights1
Definition: C4DrawGL.h:98
@ C4SSA_BoneWeights0
Definition: C4DrawGL.h:97
@ C4SSA_Position
Definition: C4DrawGL.h:89
@ C4SSU_MaterialEmission
Definition: C4DrawGL.h:75
@ C4SSU_LightTransform
Definition: C4DrawGL.h:65
@ C4SSU_FrameCounter
Definition: C4DrawGL.h:81
@ C4SSU_NormalMatrix
Definition: C4DrawGL.h:54
@ C4SSU_OverlayClr
Definition: C4DrawGL.h:62
@ C4SSU_AmbientTex
Definition: C4DrawGL.h:68
@ C4SSU_ModelViewMatrix
Definition: C4DrawGL.h:53
@ C4SSU_LightTex
Definition: C4DrawGL.h:64
@ C4SSU_Resolution
Definition: C4DrawGL.h:58
@ C4SSU_MaterialAmbient
Definition: C4DrawGL.h:72
@ C4SSU_AmbientBrightness
Definition: C4DrawGL.h:70
@ C4SSU_ClrMod
Definition: C4DrawGL.h:56
@ C4SSU_MaterialDiffuse
Definition: C4DrawGL.h:73
@ C4SSU_AmbientTransform
Definition: C4DrawGL.h:69
@ C4SSU_ProjectionMatrix
Definition: C4DrawGL.h:52
@ C4SSU_Bones
Definition: C4DrawGL.h:78
@ C4SSU_MaterialSpecular
Definition: C4DrawGL.h:74
@ C4SSU_MaterialShininess
Definition: C4DrawGL.h:76
@ C4SSU_CullMode
Definition: C4DrawGL.h:79
@ C4SSU_Gamma
Definition: C4DrawGL.h:57
#define VERTEX_OFFSET(field)
C4Game Game
Definition: C4Globals.cpp:52
C4GraphicsResource GraphicsResource
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
#define b
#define z
#define C4GFXBLIT_WIREFRAME
Definition: C4Surface.h:30
#define C4GFXBLIT_ADDITIVE
Definition: C4Surface.h:26
#define C4GFXBLIT_MOD2
Definition: C4Surface.h:27
uint32_t DWORD
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
@ SMMS_VERTEX
@ SMMS_FRAGMENT
void Rotate(MatrixType &mat, float angle, float x, float y, float z)
Definition: StdMeshMath.h:209
void Perspective(MatrixType &mat, float cot_fovy2, float aspect, float nearVal, float farVal)
Definition: StdMeshMath.h:216
void Scale(MatrixType &mat, float sx, float sy, float sz)
Definition: StdMeshMath.h:195
void Translate(MatrixType &mat, float dx, float dy, float dz)
Definition: StdMeshMath.h:185
DWORD dwBlitMode
Definition: C4Draw.h:110
bool fUsePerspective
Definition: C4Draw.h:114
float ZoomY
Definition: C4Draw.h:112
const StdMeshMatrix * MeshTransform
Definition: C4Draw.h:113
float gammaOut[3]
Definition: C4Draw.h:98
float ZoomX
Definition: C4Draw.h:112
C4Rect GetClipRect() const
Definition: C4Draw.cpp:724
C4Rect GetOutRect() const
Definition: C4Draw.cpp:733
const C4FoWRegion * pFoW
Definition: C4Draw.h:111
C4ScriptUniform scriptUniform
Definition: C4Draw.h:100
bool BlitModulated
Definition: C4Draw.h:108
DWORD BlitModulateClr
Definition: C4Draw.h:109
float Zoom
Definition: C4Draw.h:116
void GetFragTransform(const struct FLOAT_RECT &vpRect, const C4Rect &clipRect, const C4Rect &outRect, float ambientTransform[6]) const
double GetBrightness() const
Definition: C4FoWAmbient.h:56
C4FoWAmbient Ambient
Definition: C4FoW.h:115
GLuint getSurfaceName() const
const C4FoW * getFoW() const
Definition: C4FoWRegion.h:52
const FLOAT_RECT & getViewportRegion() const
Definition: C4FoWRegion.h:54
void GetFragTransform(const C4Rect &clipRect, const C4Rect &outRect, float lightTransform[6]) const
int32_t FrameCounter
Definition: C4Game.h:129
bool LoadEntryString(const char *szEntryName, StdStrBuf *rBuf)
Definition: C4GroupSet.cpp:225
C4Object * GetObject()
uint32_t BlitMode
Definition: C4Object.h:161
uint32_t ColorMod
Definition: C4Object.h:160
uint32_t Color
Definition: C4Object.h:118
Definition: C4Rect.h:28
int32_t y
Definition: C4Rect.h:30
int32_t Hgt
Definition: C4Rect.h:30
int32_t Wdt
Definition: C4Rect.h:30
int32_t x
Definition: C4Rect.h:30
void Apply(C4ShaderCall &call)
Definition: C4Shader.cpp:828
void SetUniform1f(int iUniform, float gX) const
Definition: C4Shader.h:241
void Start()
Definition: C4Shader.cpp:691
void SetUniformMatrix4x4(int iUniform, const StdMeshMatrix &matrix)
Definition: C4Shader.h:351
GLint AllocTexUnit(int iUniform)
Definition: C4Shader.cpp:668
void SetUniformMatrix4x4fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:322
void SetUniform2f(int iUniform, float gX, float gY) const
Definition: C4Shader.h:245
void Finish()
Definition: C4Shader.cpp:705
void SetUniform4fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:301
void SetUniform2fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:293
void SetUniform3fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:297
void SetUniformMatrix2x3fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:307
void SetUniform1i(int iUniform, int iX) const
Definition: C4Shader.h:209
void SetUniformMatrix3x3Transpose(int iUniform, const StdMeshMatrix &matrix)
Definition: C4Shader.h:336
GLint GetAttribute(int iAttribute) const
Definition: C4Shader.h:127
bool HaveUniform(int iUniform) const
Definition: C4Shader.h:122
GLint GetUniform(int iUniform) const
Definition: C4Shader.h:117
unsigned int texName
Definition: C4Surface.h:155
void PerformMesh(StdMeshInstance &instance, float tx, float ty, float twdt, float thgt, DWORD dwPlayerColor, C4BltTransform *pTransform) override
StdProjectionMatrix ProjectionMatrix
Definition: C4DrawGL.h:185
CStdGLCtx * pCurrCtx
Definition: C4DrawGL.h:180
bool PrepareMaterial(StdMeshMatManager &mat_manager, StdMeshMaterialLoader &loader, StdMeshMaterial &mat) override
bool GetVAO(unsigned int vaoid, GLuint &vao)
Definition: C4DrawGL.cpp:1015
struct CStdGL::@10 Workarounds
size_t GetNumSubMeshes() const
Definition: StdMesh.h:199
float GetBoundingRadius() const
Definition: StdMesh.h:207
const StdMeshBox & GetBoundingBox() const
Definition: StdMesh.h:206
GLuint GetVBO() const
Definition: StdMesh.h:212
StdMeshInstance * Child
Definition: StdMesh.h:511
Denumerator * ChildDenumerator
Definition: StdMesh.h:513
const StdMeshMatrix & GetFinalTransformation() const
Definition: StdMesh.h:518
float GetCompletion() const
Definition: StdMesh.h:554
unsigned int GetVAOID() const
Definition: StdMesh.h:626
GLuint GetIBO() const
Definition: StdMesh.h:625
size_t GetBoneCount() const
Definition: StdMesh.cpp:1463
AttachedMeshIter AttachedMeshesBegin() const
Definition: StdMesh.h:584
const StdSubMeshInstance & GetSubMeshOrdered(size_t i) const
Definition: StdMesh.h:591
const StdMesh & GetMesh() const
Definition: StdMesh.h:622
AttachedMeshIter AttachedMeshesEnd() const
Definition: StdMesh.h:585
const std::vector< StdMeshVertex > & GetSharedVertices() const
Definition: StdMesh.h:549
AttachedMeshList::const_iterator AttachedMeshIter
Definition: StdMesh.h:544
const StdMeshMatrix & GetBoneTransform(size_t i) const
Definition: StdMesh.cpp:1446
const StdMeshMaterialProgram * AddProgram(const char *name, StdMeshMaterialLoader &loader, const StdMeshMaterialPass::ShaderInstance &fragment_shader, const StdMeshMaterialPass::ShaderInstance &vertex_shader, const StdMeshMaterialPass::ShaderInstance &geometry_shader)
const StdMeshMaterialShader * AddShader(const char *filename, const char *name, const char *language, StdMeshMaterialShaderType type, const char *text, uint32_t load_flags)
StdCopyStrBuf Name
std::vector< StdMeshMaterialTechnique > Techniques
const StdMeshMaterialShader * Shader
DepthFunctionType AlphaRejectionFunction
SceneBlendType SceneBlendFactors[2]
std::shared_ptr< ProgramInstance > Program
CullHardwareType CullHardware
StdMeshMaterialShaderParameters Parameters
ShaderInstance GeometryShader
ShaderInstance FragmentShader
std::vector< StdMeshMaterialTextureUnit > TextureUnits
ShaderInstance VertexShader
const float * GetMatrix() const
const float * GetFloatv() const
StdMeshMaterialShaderParameter & AddParameter(const char *name, StdMeshMaterialShaderParameter::Type type)
std::vector< StdMeshMaterialPass > Passes
BlendOpSourceType ColorOpSources[2]
const C4TexRef & GetTexture(unsigned int i) const
TexAddressModeType TexAddressMode
BlendOpSourceType AlphaOpSources[2]
std::vector< Transformation > Transformations
static StdMeshMatrix Identity()
static StdMeshMatrix Inverse(const StdMeshMatrix &mat)
static StdMeshMatrix LookAt(const StdMeshVector &eye, const StdMeshVector &center, const StdMeshVector &up)
static StdMeshMatrix Scale(float sx, float sy, float sz)
float Determinant() const
static StdProjectionMatrix Identity()
const char * getData() const
Definition: StdBuf.h:442
void Copy()
Definition: StdBuf.h:467
size_t getLength() const
Definition: StdBuf.h:445
void Take(char *pnData)
Definition: StdBuf.h:457
size_t GetOffsetInVBO() const
Definition: StdMesh.h:173
size_t GetOffsetInIBO() const
Definition: StdMesh.h:174
const std::vector< Vertex > & GetVertices() const
Definition: StdMesh.h:163
double GetTexturePosition(size_t pass, size_t texunit) const
Definition: StdMesh.h:268
size_t GetNumFaces() const
Definition: StdMesh.h:264
const StdSubMesh & GetSubMesh() const
Definition: StdMesh.h:265
const StdMeshMaterial & GetMaterial() const
Definition: StdMesh.h:270
unsigned int GetTexturePhase(size_t pass, size_t texunit) const
Definition: StdMesh.h:267
Definition: SHA1.h:45
void process_bytes(void const *buffer, std::size_t byte_count)
Definition: SHA1.h:111
void get_digest(digest_type digest)
Definition: SHA1.h:169
void Up(T &keys)
float y2
Definition: StdMesh.h:147
float z1
Definition: StdMesh.h:146
float z2
Definition: StdMesh.h:147
float x2
Definition: StdMesh.h:147
float y1
Definition: StdMesh.h:146
float x1
Definition: StdMesh.h:146
static StdMeshVector Translate(float dx, float dy, float dz)
Definition: StdMeshMath.cpp:38
uint16_t bone_index[MaxBoneWeightCount]
Definition: StdMeshMath.h:45
static const size_t MaxBoneWeightCount
Definition: StdMeshMath.h:37
float bone_weight[MaxBoneWeightCount]
Definition: StdMeshMath.h:44