Render the casters into each cascade layer of the depth array, once per cascade with that cascade's light-VP: solid triangles with the depth-only program, then the alpha-cutout batches with the texture-alpha-discard program (one bind per caster texture). Depth/clear go through the Core bundles so the GL-state audits stay green; ApplyPipeline re-owns depth/cull/texture on the next draw.
| 313 | // caster texture). Depth/clear go through the Core bundles so the GL-state audits |
| 314 | // stay green; ApplyPipeline re-owns depth/cull/texture on the next draw. |
| 315 | bool RenderCascadeArray(void* glContext, const float* lightVPs, int numCascades, int res, const float* solidXYZ, |
| 316 | int solidVertCount, const float* alphaXYZUV, int alphaVertCount, |
| 317 | const ResolvedAlphaBatch* batches, int batchCount) |
| 318 | { |
| 319 | const bool haveSolid = solidXYZ && solidVertCount >= 3; |
| 320 | const bool haveAlpha = alphaXYZUV && alphaVertCount >= 3 && batches && batchCount > 0; |
| 321 | if (!glContext || !lightVPs || res <= 0 || numCascades < 1 || (!haveSolid && !haveAlpha)) |
| 322 | return false; |
| 323 | if (!EnsureProgram() || !EnsureArrayTarget(res, numCascades) || !EnsureMesh()) |
| 324 | return false; |
| 325 | if (haveAlpha && (!EnsureAlphaProgram() || !EnsureAlphaMesh())) |
| 326 | return false; |
| 327 | |
| 328 | GLint prevFBO = 0, prevProg = 0, prevVAO = 0, prevArrayBuf = 0; |
| 329 | GLint prevVP[4] = {0, 0, 0, 0}; |
| 330 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFBO); |
| 331 | glGetIntegerv(GL_CURRENT_PROGRAM, &prevProg); |
| 332 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prevVAO); |
| 333 | glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuf); |
| 334 | glGetIntegerv(GL_VIEWPORT, prevVP); |
| 335 | |
| 336 | glBindFramebuffer(GL_FRAMEBUFFER, s_arrFbo); |
| 337 | glViewport(0, 0, res, res); |
| 338 | Poseidon::render::depthstencil::Normal(/*hasStencil*/ false); |
| 339 | Poseidon::render::cull::None(); |
| 340 | |
| 341 | // Upload the (cascade-invariant) geometry once; only the light-VP changes per layer. |
| 342 | if (haveSolid) |
| 343 | { |
| 344 | GL33Bind::Vao(s_vao); |
| 345 | glBindBuffer(GL_ARRAY_BUFFER, s_vbo); |
| 346 | glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(solidVertCount) * 3 * sizeof(float), solidXYZ, |
| 347 | GL_DYNAMIC_DRAW); |
| 348 | } |
| 349 | if (haveAlpha) |
| 350 | { |
| 351 | GL33Bind::Vao(s_alphaVao); |
| 352 | glBindBuffer(GL_ARRAY_BUFFER, s_alphaVbo); |
| 353 | glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(alphaVertCount) * 5 * sizeof(float), alphaXYZUV, |
| 354 | GL_DYNAMIC_DRAW); |
| 355 | } |
| 356 | |
| 357 | glClearDepth(1.0); |
| 358 | for (int i = 0; i < numCascades; i++) |
| 359 | { |
| 360 | glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, s_arrTex, 0, i); |
| 361 | Poseidon::render::clear::WithMask(GL_DEPTH_BUFFER_BIT); |
| 362 | |
| 363 | if (haveSolid) |
| 364 | { |
| 365 | // Store the BACK faces (cull front): a lit front-facing surface is then |
| 366 | // strictly nearer the light than the stored depth, so it cannot |
| 367 | // self-shadow — kills the soldier's self-shadow acne/animation flicker |
| 368 | // and lets the bias stay tiny (no peter-panning / shadows vanishing up |
| 369 | // close). ApplyPipeline re-owns cull on the next draw. |
| 370 | Poseidon::render::cull::Front(); |
| 371 | glUseProgram(s_prog); |
| 372 | glUniformMatrix4fv(s_locVP, 1, GL_FALSE, lightVPs + i * 16); |
no test coverage detected