#include "backend.h" #include "../draw_queue.h" #include "../geom/selection_disc.h" #include "../geom/selection_ring.h" #include "../primitive_batch.h" #include "backend/character_pipeline.h" #include "backend/cylinder_pipeline.h" #include "backend/effects_pipeline.h" #include "backend/primitive_batch_pipeline.h" #include "backend/terrain_pipeline.h" #include "backend/vegetation_pipeline.h" #include "backend/water_pipeline.h" #include "buffer.h" #include "gl/camera.h" #include "gl/resources.h" #include "ground/firecamp_gpu.h" #include "ground/grass_gpu.h" #include "ground/olive_gpu.h" #include "ground/pine_gpu.h" #include "ground/plant_gpu.h" #include "ground/stone_gpu.h" #include "mesh.h" #include "render_constants.h" #include "shader.h" #include "state_scopes.h" #include "texture.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace Render::GL { using namespace Render::GL::ColorIndex; using namespace Render::GL::VertexAttrib; using namespace Render::GL::ComponentCount; namespace { const QVector3D k_grid_line_color(0.22F, 0.25F, 0.22F); } Backend::Backend() = default; Backend::~Backend() { if (QOpenGLContext::currentContext() == nullptr) { (void)m_cylinderPipeline.release(); (void)m_vegetationPipeline.release(); (void)m_terrainPipeline.release(); (void)m_characterPipeline.release(); (void)m_waterPipeline.release(); (void)m_effectsPipeline.release(); } else { m_cylinderPipeline.reset(); m_vegetationPipeline.reset(); m_terrainPipeline.reset(); m_characterPipeline.reset(); m_waterPipeline.reset(); m_effectsPipeline.reset(); } } void Backend::initialize() { qInfo() << "Backend::initialize() - Starting..."; qInfo() << "Backend: Initializing OpenGL functions..."; initializeOpenGLFunctions(); qInfo() << "Backend: OpenGL functions initialized"; qInfo() << "Backend: Setting up depth test..."; glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glDepthRange(0.0, 1.0); glDepthMask(GL_TRUE); qInfo() << "Backend: Setting up blending..."; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); qInfo() << "Backend: Creating ResourceManager..."; m_resources = std::make_unique(); if (!m_resources->initialize()) { qWarning() << "Backend: failed to initialize ResourceManager"; } qInfo() << "Backend: ResourceManager created"; qInfo() << "Backend: Creating ShaderCache..."; m_shaderCache = std::make_unique(); m_shaderCache->initializeDefaults(); qInfo() << "Backend: ShaderCache created"; qInfo() << "Backend: Creating CylinderPipeline..."; m_cylinderPipeline = std::make_unique(m_shaderCache.get()); m_cylinderPipeline->initialize(); qInfo() << "Backend: CylinderPipeline initialized"; qInfo() << "Backend: Creating VegetationPipeline..."; m_vegetationPipeline = std::make_unique( m_shaderCache.get()); m_vegetationPipeline->initialize(); qInfo() << "Backend: VegetationPipeline initialized"; qInfo() << "Backend: Creating TerrainPipeline..."; m_terrainPipeline = std::make_unique( this, m_shaderCache.get()); m_terrainPipeline->initialize(); qInfo() << "Backend: TerrainPipeline initialized"; qInfo() << "Backend: Creating CharacterPipeline..."; m_characterPipeline = std::make_unique( this, m_shaderCache.get()); m_characterPipeline->initialize(); qInfo() << "Backend: CharacterPipeline initialized"; qInfo() << "Backend: Creating WaterPipeline..."; m_waterPipeline = std::make_unique( this, m_shaderCache.get()); m_waterPipeline->initialize(); qInfo() << "Backend: WaterPipeline initialized"; qInfo() << "Backend: Creating EffectsPipeline..."; m_effectsPipeline = std::make_unique( this, m_shaderCache.get()); m_effectsPipeline->initialize(); qInfo() << "Backend: EffectsPipeline initialized"; qInfo() << "Backend: Creating PrimitiveBatchPipeline..."; m_primitiveBatchPipeline = std::make_unique( m_shaderCache.get()); m_primitiveBatchPipeline->initialize(); qInfo() << "Backend: PrimitiveBatchPipeline initialized"; qInfo() << "Backend: Loading basic shaders..."; m_basicShader = m_shaderCache->get(QStringLiteral("basic")); m_gridShader = m_shaderCache->get(QStringLiteral("grid")); if (m_basicShader == nullptr) { qWarning() << "Backend: basic shader missing"; } if (m_gridShader == nullptr) { qWarning() << "Backend: grid shader missing"; } qInfo() << "Backend::initialize() - Complete!"; } void Backend::beginFrame() { if (m_viewportWidth > 0 && m_viewportHeight > 0) { glViewport(0, 0, m_viewportWidth, m_viewportHeight); } glClearColor(m_clearColor[Red], m_clearColor[Green], m_clearColor[Blue], m_clearColor[Alpha]); glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); if (m_cylinderPipeline) { m_cylinderPipeline->beginFrame(); } } void Backend::setViewport(int w, int h) { m_viewportWidth = w; m_viewportHeight = h; } void Backend::setClearColor(float r, float g, float b, float a) { m_clearColor[Red] = r; m_clearColor[Green] = g; m_clearColor[Blue] = b; m_clearColor[Alpha] = a; } void Backend::execute(const DrawQueue &queue, const Camera &cam) { if (m_basicShader == nullptr) { return; } const QMatrix4x4 view_proj = cam.getProjectionMatrix() * cam.getViewMatrix(); m_lastBoundShader = nullptr; m_lastBoundTexture = nullptr; const std::size_t count = queue.size(); std::size_t i = 0; while (i < count) { const auto &cmd = queue.getSorted(i); switch (cmd.index()) { case CylinderCmdIndex: { if (!m_cylinderPipeline) { ++i; continue; } m_cylinderPipeline->m_cylinderScratch.clear(); do { const auto &cy = std::get(queue.getSorted(i)); BackendPipelines::CylinderPipeline::CylinderInstanceGpu gpu{}; gpu.start = cy.start; gpu.end = cy.end; gpu.radius = cy.radius; gpu.alpha = cy.alpha; gpu.color = cy.color; m_cylinderPipeline->m_cylinderScratch.emplace_back(gpu); ++i; } while (i < count && queue.getSorted(i).index() == CylinderCmdIndex); const std::size_t instance_count = m_cylinderPipeline->m_cylinderScratch.size(); if (instance_count > 0 && (m_cylinderPipeline->cylinderShader() != nullptr)) { glDepthMask(GL_TRUE); if (glIsEnabled(GL_POLYGON_OFFSET_FILL) != 0U) { glDisable(GL_POLYGON_OFFSET_FILL); } Shader *cylinder_shader = m_cylinderPipeline->cylinderShader(); if (m_lastBoundShader != cylinder_shader) { cylinder_shader->use(); m_lastBoundShader = cylinder_shader; m_lastBoundTexture = nullptr; } if (m_cylinderPipeline->m_cylinderUniforms.view_proj != Shader::InvalidUniform) { cylinder_shader->setUniform( m_cylinderPipeline->m_cylinderUniforms.view_proj, view_proj); } m_cylinderPipeline->uploadCylinderInstances(instance_count); m_cylinderPipeline->draw_cylinders(instance_count); } continue; } case FogBatchCmdIndex: { if (!m_cylinderPipeline) { ++i; continue; } const auto &batch = std::get(cmd); const FogInstanceData *instances = batch.instances; const std::size_t instance_count = batch.count; if ((instances != nullptr) && instance_count > 0 && (m_cylinderPipeline->fogShader() != nullptr)) { m_cylinderPipeline->m_fogScratch.resize(instance_count); for (std::size_t idx = 0; idx < instance_count; ++idx) { BackendPipelines::CylinderPipeline::FogInstanceGpu gpu{}; gpu.center = instances[idx].center; gpu.size = instances[idx].size; gpu.color = instances[idx].color; gpu.alpha = instances[idx].alpha; m_cylinderPipeline->m_fogScratch[idx] = gpu; } glDepthMask(GL_TRUE); if (glIsEnabled(GL_POLYGON_OFFSET_FILL) != 0U) { glDisable(GL_POLYGON_OFFSET_FILL); } Shader *fog_shader = m_cylinderPipeline->fogShader(); if (m_lastBoundShader != fog_shader) { fog_shader->use(); m_lastBoundShader = fog_shader; m_lastBoundTexture = nullptr; } if (m_cylinderPipeline->m_fogUniforms.view_proj != Shader::InvalidUniform) { fog_shader->setUniform(m_cylinderPipeline->m_fogUniforms.view_proj, view_proj); } m_cylinderPipeline->uploadFogInstances(instance_count); m_cylinderPipeline->drawFog(instance_count); } ++i; continue; } case GrassBatchCmdIndex: { const auto &grass = std::get(cmd); if ((grass.instanceBuffer == nullptr) || grass.instance_count == 0 || (m_terrainPipeline->m_grassShader == nullptr) || (m_terrainPipeline->m_grassVao == 0U) || m_terrainPipeline->m_grassVertexCount == 0) { break; } DepthMaskScope const depth_mask(false); BlendScope const blend(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLboolean const prev_cull = glIsEnabled(GL_CULL_FACE); if (prev_cull != 0U) { glDisable(GL_CULL_FACE); } if (m_lastBoundShader != m_terrainPipeline->m_grassShader) { m_terrainPipeline->m_grassShader->use(); m_lastBoundShader = m_terrainPipeline->m_grassShader; m_lastBoundTexture = nullptr; } if (m_terrainPipeline->m_grassUniforms.view_proj != Shader::InvalidUniform) { m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.view_proj, view_proj); } if (m_terrainPipeline->m_grassUniforms.time != Shader::InvalidUniform) { m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.time, grass.params.time); } if (m_terrainPipeline->m_grassUniforms.wind_strength != Shader::InvalidUniform) { m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.wind_strength, grass.params.wind_strength); } if (m_terrainPipeline->m_grassUniforms.wind_speed != Shader::InvalidUniform) { m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.wind_speed, grass.params.wind_speed); } if (m_terrainPipeline->m_grassUniforms.soil_color != Shader::InvalidUniform) { m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.soil_color, grass.params.soil_color); } if (m_terrainPipeline->m_grassUniforms.light_dir != Shader::InvalidUniform) { QVector3D light_dir = grass.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } m_terrainPipeline->m_grassShader->setUniform( m_terrainPipeline->m_grassUniforms.light_dir, light_dir); } glBindVertexArray(m_terrainPipeline->m_grassVao); grass.instanceBuffer->bind(); const auto stride = static_cast(sizeof(GrassInstanceGpu)); glVertexAttribPointer( TexCoord, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(GrassInstanceGpu, posHeight))); glVertexAttribPointer( InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(GrassInstanceGpu, colorWidth))); glVertexAttribPointer( InstanceScale, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(GrassInstanceGpu, swayParams))); grass.instanceBuffer->unbind(); glDrawArraysInstanced(GL_TRIANGLES, 0, m_terrainPipeline->m_grassVertexCount, static_cast(grass.instance_count)); glBindVertexArray(0); if (prev_cull != 0U) { glEnable(GL_CULL_FACE); } break; } case StoneBatchCmdIndex: { if (!m_vegetationPipeline) { ++i; continue; } const auto &stone = std::get(cmd); if ((stone.instanceBuffer == nullptr) || stone.instance_count == 0 || (m_vegetationPipeline->stoneShader() == nullptr) || (m_vegetationPipeline->m_stoneVao == 0U) || m_vegetationPipeline->m_stoneIndexCount == 0) { break; } DepthMaskScope const depth_mask(true); BlendScope const blend(false); Shader *stone_shader = m_vegetationPipeline->stoneShader(); if (m_lastBoundShader != stone_shader) { stone_shader->use(); m_lastBoundShader = stone_shader; m_lastBoundTexture = nullptr; } if (m_vegetationPipeline->m_stoneUniforms.view_proj != Shader::InvalidUniform) { stone_shader->setUniform( m_vegetationPipeline->m_stoneUniforms.view_proj, view_proj); } if (m_vegetationPipeline->m_stoneUniforms.light_direction != Shader::InvalidUniform) { QVector3D light_dir = stone.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } stone_shader->setUniform( m_vegetationPipeline->m_stoneUniforms.light_direction, light_dir); } glBindVertexArray(m_vegetationPipeline->m_stoneVao); stone.instanceBuffer->bind(); const auto stride = static_cast(sizeof(StoneInstanceGpu)); glVertexAttribPointer( TexCoord, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(StoneInstanceGpu, posScale))); glVertexAttribPointer( InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(StoneInstanceGpu, colorRot))); stone.instanceBuffer->unbind(); glDrawElementsInstanced(GL_TRIANGLES, m_vegetationPipeline->m_stoneIndexCount, GL_UNSIGNED_SHORT, nullptr, static_cast(stone.instance_count)); glBindVertexArray(0); break; } case PlantBatchCmdIndex: { if (!m_vegetationPipeline) { ++i; continue; } const auto &plant = std::get(cmd); if ((plant.instanceBuffer == nullptr) || plant.instance_count == 0 || (m_vegetationPipeline->plantShader() == nullptr) || (m_vegetationPipeline->m_plantVao == 0U) || m_vegetationPipeline->m_plantIndexCount == 0) { break; } DepthMaskScope const depth_mask(true); glEnable(GL_DEPTH_TEST); BlendScope const blend(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLboolean const prev_cull = glIsEnabled(GL_CULL_FACE); if (prev_cull != 0U) { glDisable(GL_CULL_FACE); } Shader *plant_shader = m_vegetationPipeline->plantShader(); if (m_lastBoundShader != plant_shader) { plant_shader->use(); m_lastBoundShader = plant_shader; m_lastBoundTexture = nullptr; } if (m_vegetationPipeline->m_plantUniforms.view_proj != Shader::InvalidUniform) { plant_shader->setUniform( m_vegetationPipeline->m_plantUniforms.view_proj, view_proj); } if (m_vegetationPipeline->m_plantUniforms.time != Shader::InvalidUniform) { plant_shader->setUniform(m_vegetationPipeline->m_plantUniforms.time, plant.params.time); } if (m_vegetationPipeline->m_plantUniforms.wind_strength != Shader::InvalidUniform) { plant_shader->setUniform( m_vegetationPipeline->m_plantUniforms.wind_strength, plant.params.wind_strength); } if (m_vegetationPipeline->m_plantUniforms.wind_speed != Shader::InvalidUniform) { plant_shader->setUniform( m_vegetationPipeline->m_plantUniforms.wind_speed, plant.params.wind_speed); } if (m_vegetationPipeline->m_plantUniforms.light_direction != Shader::InvalidUniform) { QVector3D light_dir = plant.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } plant_shader->setUniform( m_vegetationPipeline->m_plantUniforms.light_direction, light_dir); } glBindVertexArray(m_vegetationPipeline->m_plantVao); plant.instanceBuffer->bind(); const auto stride = static_cast(sizeof(PlantInstanceGpu)); glVertexAttribPointer( InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PlantInstanceGpu, posScale))); glVertexAttribPointer( InstanceScale, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PlantInstanceGpu, colorSway))); glVertexAttribPointer( InstanceColor, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PlantInstanceGpu, typeParams))); plant.instanceBuffer->unbind(); glDrawElementsInstanced(GL_TRIANGLES, m_vegetationPipeline->m_plantIndexCount, GL_UNSIGNED_SHORT, nullptr, static_cast(plant.instance_count)); glBindVertexArray(0); if (prev_cull != 0U) { glEnable(GL_CULL_FACE); } break; } case PineBatchCmdIndex: { if (!m_vegetationPipeline) { ++i; continue; } const auto &pine = std::get(cmd); if ((pine.instanceBuffer == nullptr) || pine.instance_count == 0 || (m_vegetationPipeline->pineShader() == nullptr) || (m_vegetationPipeline->m_pineVao == 0U) || m_vegetationPipeline->m_pineIndexCount == 0) { break; } DepthMaskScope const depth_mask(true); glEnable(GL_DEPTH_TEST); BlendScope const blend(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLboolean const prev_cull = glIsEnabled(GL_CULL_FACE); if (prev_cull != 0U) { glDisable(GL_CULL_FACE); } Shader *pine_shader = m_vegetationPipeline->pineShader(); if (m_lastBoundShader != pine_shader) { pine_shader->use(); m_lastBoundShader = pine_shader; m_lastBoundTexture = nullptr; } if (m_vegetationPipeline->m_pineUniforms.view_proj != Shader::InvalidUniform) { pine_shader->setUniform(m_vegetationPipeline->m_pineUniforms.view_proj, view_proj); } if (m_vegetationPipeline->m_pineUniforms.time != Shader::InvalidUniform) { pine_shader->setUniform(m_vegetationPipeline->m_pineUniforms.time, pine.params.time); } if (m_vegetationPipeline->m_pineUniforms.wind_strength != Shader::InvalidUniform) { pine_shader->setUniform( m_vegetationPipeline->m_pineUniforms.wind_strength, pine.params.wind_strength); } if (m_vegetationPipeline->m_pineUniforms.wind_speed != Shader::InvalidUniform) { pine_shader->setUniform(m_vegetationPipeline->m_pineUniforms.wind_speed, pine.params.wind_speed); } if (m_vegetationPipeline->m_pineUniforms.light_direction != Shader::InvalidUniform) { QVector3D light_dir = pine.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } pine_shader->setUniform( m_vegetationPipeline->m_pineUniforms.light_direction, light_dir); } glBindVertexArray(m_vegetationPipeline->m_pineVao); pine.instanceBuffer->bind(); const auto stride = static_cast(sizeof(PineInstanceGpu)); glVertexAttribPointer( InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PineInstanceGpu, posScale))); glVertexAttribPointer( InstanceScale, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PineInstanceGpu, colorSway))); glVertexAttribPointer( InstanceColor, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(PineInstanceGpu, rotation))); pine.instanceBuffer->unbind(); glDrawElementsInstanced(GL_TRIANGLES, m_vegetationPipeline->m_pineIndexCount, GL_UNSIGNED_SHORT, nullptr, static_cast(pine.instance_count)); glBindVertexArray(0); if (prev_cull != 0U) { glEnable(GL_CULL_FACE); } break; } case OliveBatchCmdIndex: { if (!m_vegetationPipeline) { ++i; continue; } const auto &olive = std::get(cmd); if ((olive.instanceBuffer == nullptr) || olive.instance_count == 0 || (m_vegetationPipeline->oliveShader() == nullptr) || (m_vegetationPipeline->m_oliveVao == 0U) || m_vegetationPipeline->m_oliveIndexCount == 0) { break; } DepthMaskScope const depth_mask(true); glEnable(GL_DEPTH_TEST); BlendScope const blend(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLboolean const prev_cull = glIsEnabled(GL_CULL_FACE); if (prev_cull != 0U) { glDisable(GL_CULL_FACE); } Shader *olive_shader = m_vegetationPipeline->oliveShader(); if (m_lastBoundShader != olive_shader) { olive_shader->use(); m_lastBoundShader = olive_shader; m_lastBoundTexture = nullptr; } if (m_vegetationPipeline->m_oliveUniforms.view_proj != Shader::InvalidUniform) { olive_shader->setUniform( m_vegetationPipeline->m_oliveUniforms.view_proj, view_proj); } if (m_vegetationPipeline->m_oliveUniforms.time != Shader::InvalidUniform) { olive_shader->setUniform(m_vegetationPipeline->m_oliveUniforms.time, olive.params.time); } if (m_vegetationPipeline->m_oliveUniforms.wind_strength != Shader::InvalidUniform) { olive_shader->setUniform( m_vegetationPipeline->m_oliveUniforms.wind_strength, olive.params.wind_strength); } if (m_vegetationPipeline->m_oliveUniforms.wind_speed != Shader::InvalidUniform) { olive_shader->setUniform( m_vegetationPipeline->m_oliveUniforms.wind_speed, olive.params.wind_speed); } if (m_vegetationPipeline->m_oliveUniforms.light_direction != Shader::InvalidUniform) { QVector3D light_dir = olive.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } olive_shader->setUniform( m_vegetationPipeline->m_oliveUniforms.light_direction, light_dir); } glBindVertexArray(m_vegetationPipeline->m_oliveVao); olive.instanceBuffer->bind(); const auto stride = static_cast(sizeof(OliveInstanceGpu)); glVertexAttribPointer( InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(OliveInstanceGpu, pos_scale))); glVertexAttribPointer( InstanceScale, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(OliveInstanceGpu, color_sway))); glVertexAttribPointer( InstanceColor, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(OliveInstanceGpu, rotation))); olive.instanceBuffer->unbind(); glDrawElementsInstanced(GL_TRIANGLES, m_vegetationPipeline->m_oliveIndexCount, GL_UNSIGNED_SHORT, nullptr, static_cast(olive.instance_count)); glBindVertexArray(0); if (prev_cull != 0U) { glEnable(GL_CULL_FACE); } break; } case FireCampBatchCmdIndex: { if (!m_vegetationPipeline) { ++i; continue; } const auto &firecamp = std::get(cmd); if ((firecamp.instanceBuffer == nullptr) || firecamp.instance_count == 0 || (m_vegetationPipeline->firecampShader() == nullptr) || (m_vegetationPipeline->m_firecampVao == 0U) || m_vegetationPipeline->m_firecampIndexCount == 0) { break; } DepthMaskScope const depth_mask(true); glEnable(GL_DEPTH_TEST); BlendScope const blend(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLboolean const prev_cull = glIsEnabled(GL_CULL_FACE); if (prev_cull != 0U) { glDisable(GL_CULL_FACE); } Shader *firecamp_shader = m_vegetationPipeline->firecampShader(); if (m_lastBoundShader != firecamp_shader) { firecamp_shader->use(); m_lastBoundShader = firecamp_shader; m_lastBoundTexture = nullptr; } if (m_vegetationPipeline->m_firecampUniforms.view_proj != Shader::InvalidUniform) { firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.view_proj, view_proj); } if (m_vegetationPipeline->m_firecampUniforms.time != Shader::InvalidUniform) { firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.time, firecamp.params.time); } if (m_vegetationPipeline->m_firecampUniforms.flickerSpeed != Shader::InvalidUniform) { firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.flickerSpeed, firecamp.params.flickerSpeed); } if (m_vegetationPipeline->m_firecampUniforms.flickerAmount != Shader::InvalidUniform) { firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.flickerAmount, firecamp.params.flickerAmount); } if (m_vegetationPipeline->m_firecampUniforms.glowStrength != Shader::InvalidUniform) { firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.glowStrength, firecamp.params.glowStrength); } if (m_vegetationPipeline->m_firecampUniforms.camera_right != Shader::InvalidUniform) { QVector3D camera_right = cam.getRightVector(); if (camera_right.lengthSquared() < 1e-6F) { camera_right = QVector3D(1.0F, 0.0F, 0.0F); } else { camera_right.normalize(); } firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.camera_right, camera_right); } if (m_vegetationPipeline->m_firecampUniforms.camera_forward != Shader::InvalidUniform) { QVector3D camera_forward = cam.getForwardVector(); if (camera_forward.lengthSquared() < 1e-6F) { camera_forward = QVector3D(0.0F, 0.0F, -1.0F); } else { camera_forward.normalize(); } firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.camera_forward, camera_forward); } if (m_vegetationPipeline->m_firecampUniforms.fireTexture != Shader::InvalidUniform) { if (m_resources && (m_resources->white() != nullptr)) { m_resources->white()->bind(0); firecamp_shader->setUniform( m_vegetationPipeline->m_firecampUniforms.fireTexture, 0); } } glBindVertexArray(m_vegetationPipeline->m_firecampVao); firecamp.instanceBuffer->bind(); const auto stride = static_cast(sizeof(FireCampInstanceGpu)); glVertexAttribPointer(InstancePosition, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast( offsetof(FireCampInstanceGpu, pos_intensity))); glVertexAttribPointer(InstanceScale, Vec4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast( offsetof(FireCampInstanceGpu, radius_phase))); firecamp.instanceBuffer->unbind(); glDrawElementsInstanced(GL_TRIANGLES, m_vegetationPipeline->m_firecampIndexCount, GL_UNSIGNED_SHORT, nullptr, static_cast(firecamp.instance_count)); glBindVertexArray(0); if (prev_cull != 0U) { glEnable(GL_CULL_FACE); } break; } case TerrainChunkCmdIndex: { const auto &terrain = std::get(cmd); Shader *active_shader = terrain.params.is_ground_plane ? m_terrainPipeline->m_groundShader : m_terrainPipeline->m_terrainShader; if ((terrain.mesh == nullptr) || (active_shader == nullptr)) { break; } if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; m_lastBoundTexture = nullptr; } const QMatrix4x4 mvp = view_proj * terrain.model; if (terrain.params.is_ground_plane) { if (m_terrainPipeline->m_groundUniforms.mvp != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_groundUniforms.mvp, mvp); } if (m_terrainPipeline->m_groundUniforms.model != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_groundUniforms.model, terrain.model); } if (m_terrainPipeline->m_groundUniforms.grass_primary != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.grass_primary, terrain.params.grass_primary); } if (m_terrainPipeline->m_groundUniforms.grass_secondary != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.grass_secondary, terrain.params.grass_secondary); } if (m_terrainPipeline->m_groundUniforms.grass_dry != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.grass_dry, terrain.params.grass_dry); } if (m_terrainPipeline->m_groundUniforms.soil_color != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.soil_color, terrain.params.soil_color); } if (m_terrainPipeline->m_groundUniforms.tint != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_groundUniforms.tint, terrain.params.tint); } if (m_terrainPipeline->m_groundUniforms.noise_offset != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.noise_offset, terrain.params.noise_offset); } if (m_terrainPipeline->m_groundUniforms.tile_size != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.tile_size, terrain.params.tile_size); } if (m_terrainPipeline->m_groundUniforms.macro_noise_scale != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.macro_noise_scale, terrain.params.macro_noise_scale); } if (m_terrainPipeline->m_groundUniforms.detail_noise_scale != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.detail_noise_scale, terrain.params.detail_noise_scale); } if (m_terrainPipeline->m_groundUniforms.soil_blend_height != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.soil_blend_height, terrain.params.soil_blend_height); } if (m_terrainPipeline->m_groundUniforms.soil_blend_sharpness != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.soil_blend_sharpness, terrain.params.soil_blend_sharpness); } if (m_terrainPipeline->m_groundUniforms.height_noise_strength != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.height_noise_strength, terrain.params.height_noise_strength); } if (m_terrainPipeline->m_groundUniforms.height_noise_frequency != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.height_noise_frequency, terrain.params.height_noise_frequency); } if (m_terrainPipeline->m_groundUniforms.ambient_boost != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.ambient_boost, terrain.params.ambient_boost); } if (m_terrainPipeline->m_groundUniforms.light_dir != Shader::InvalidUniform) { QVector3D light_dir = terrain.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } active_shader->setUniform( m_terrainPipeline->m_groundUniforms.light_dir, light_dir); } if (m_terrainPipeline->m_groundUniforms.snow_coverage != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.snow_coverage, terrain.params.snow_coverage); } if (m_terrainPipeline->m_groundUniforms.moisture_level != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.moisture_level, terrain.params.moisture_level); } if (m_terrainPipeline->m_groundUniforms.crack_intensity != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.crack_intensity, terrain.params.crack_intensity); } if (m_terrainPipeline->m_groundUniforms.grass_saturation != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.grass_saturation, terrain.params.grass_saturation); } if (m_terrainPipeline->m_groundUniforms.soil_roughness != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.soil_roughness, terrain.params.soil_roughness); } if (m_terrainPipeline->m_groundUniforms.snow_color != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_groundUniforms.snow_color, terrain.params.snow_color); } } else { if (m_terrainPipeline->m_terrainUniforms.mvp != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_terrainUniforms.mvp, mvp); } if (m_terrainPipeline->m_terrainUniforms.model != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_terrainUniforms.model, terrain.model); } if (m_terrainPipeline->m_terrainUniforms.grass_primary != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.grass_primary, terrain.params.grass_primary); } if (m_terrainPipeline->m_terrainUniforms.grass_secondary != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.grass_secondary, terrain.params.grass_secondary); } if (m_terrainPipeline->m_terrainUniforms.grass_dry != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.grass_dry, terrain.params.grass_dry); } if (m_terrainPipeline->m_terrainUniforms.soil_color != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.soil_color, terrain.params.soil_color); } if (m_terrainPipeline->m_terrainUniforms.rock_low != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.rock_low, terrain.params.rock_low); } if (m_terrainPipeline->m_terrainUniforms.rock_high != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.rock_high, terrain.params.rock_high); } if (m_terrainPipeline->m_terrainUniforms.tint != Shader::InvalidUniform) { active_shader->setUniform(m_terrainPipeline->m_terrainUniforms.tint, terrain.params.tint); } if (m_terrainPipeline->m_terrainUniforms.noise_offset != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.noise_offset, terrain.params.noise_offset); } if (m_terrainPipeline->m_terrainUniforms.tile_size != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.tile_size, terrain.params.tile_size); } if (m_terrainPipeline->m_terrainUniforms.macro_noise_scale != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.macro_noise_scale, terrain.params.macro_noise_scale); } if (m_terrainPipeline->m_terrainUniforms.detail_noise_scale != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.detail_noise_scale, terrain.params.detail_noise_scale); } if (m_terrainPipeline->m_terrainUniforms.slope_rock_threshold != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.slope_rock_threshold, terrain.params.slope_rock_threshold); } if (m_terrainPipeline->m_terrainUniforms.slope_rock_sharpness != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.slope_rock_sharpness, terrain.params.slope_rock_sharpness); } if (m_terrainPipeline->m_terrainUniforms.soil_blend_height != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.soil_blend_height, terrain.params.soil_blend_height); } if (m_terrainPipeline->m_terrainUniforms.soil_blend_sharpness != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.soil_blend_sharpness, terrain.params.soil_blend_sharpness); } if (m_terrainPipeline->m_terrainUniforms.height_noise_strength != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.height_noise_strength, terrain.params.height_noise_strength); } if (m_terrainPipeline->m_terrainUniforms.height_noise_frequency != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.height_noise_frequency, terrain.params.height_noise_frequency); } if (m_terrainPipeline->m_terrainUniforms.ambient_boost != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.ambient_boost, terrain.params.ambient_boost); } if (m_terrainPipeline->m_terrainUniforms.rock_detail_strength != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.rock_detail_strength, terrain.params.rock_detail_strength); } if (m_terrainPipeline->m_terrainUniforms.light_dir != Shader::InvalidUniform) { QVector3D light_dir = terrain.params.light_direction; if (!light_dir.isNull()) { light_dir.normalize(); } active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.light_dir, light_dir); } if (m_terrainPipeline->m_terrainUniforms.snow_coverage != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.snow_coverage, terrain.params.snow_coverage); } if (m_terrainPipeline->m_terrainUniforms.moisture_level != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.moisture_level, terrain.params.moisture_level); } if (m_terrainPipeline->m_terrainUniforms.crack_intensity != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.crack_intensity, terrain.params.crack_intensity); } if (m_terrainPipeline->m_terrainUniforms.rock_exposure != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.rock_exposure, terrain.params.rock_exposure); } if (m_terrainPipeline->m_terrainUniforms.grass_saturation != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.grass_saturation, terrain.params.grass_saturation); } if (m_terrainPipeline->m_terrainUniforms.soil_roughness != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.soil_roughness, terrain.params.soil_roughness); } if (m_terrainPipeline->m_terrainUniforms.snow_color != Shader::InvalidUniform) { active_shader->setUniform( m_terrainPipeline->m_terrainUniforms.snow_color, terrain.params.snow_color); } } DepthMaskScope const depth_mask(terrain.depthWrite); std::unique_ptr poly_scope; if (terrain.depthBias != 0.0F) { poly_scope = std::make_unique(terrain.depthBias, terrain.depthBias); } terrain.mesh->draw(); break; } case MeshCmdIndex: { const auto &it = std::get(cmd); if (it.mesh == nullptr) { break; } Shader *active_shader = (it.shader != nullptr) ? it.shader : m_basicShader; if (active_shader == nullptr) { break; } if (glIsEnabled(GL_POLYGON_OFFSET_FILL) != 0U) { glDisable(GL_POLYGON_OFFSET_FILL); } Shader *shadowShader = m_shaderCache ? m_shaderCache->get(QStringLiteral("troop_shadow")) : nullptr; bool const isShadowShader = (active_shader == shadowShader); std::unique_ptr shadow_depth_scope; std::unique_ptr shadow_blend_scope; if (isShadowShader) { shadow_depth_scope = std::make_unique(false); shadow_blend_scope = std::make_unique(true); } else { glDepthMask(GL_TRUE); } if (active_shader == m_waterPipeline->m_riverShader) { if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; } active_shader->setUniform(m_waterPipeline->m_riverUniforms.model, it.model); active_shader->setUniform(m_waterPipeline->m_riverUniforms.view, cam.getViewMatrix()); active_shader->setUniform(m_waterPipeline->m_riverUniforms.projection, cam.getProjectionMatrix()); active_shader->setUniform(m_waterPipeline->m_riverUniforms.time, m_animationTime); it.mesh->draw(); break; } if (active_shader == m_waterPipeline->m_riverbankShader) { if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; } active_shader->setUniform(m_waterPipeline->m_riverbankUniforms.model, it.model); active_shader->setUniform(m_waterPipeline->m_riverbankUniforms.view, cam.getViewMatrix()); active_shader->setUniform( m_waterPipeline->m_riverbankUniforms.projection, cam.getProjectionMatrix()); active_shader->setUniform(m_waterPipeline->m_riverbankUniforms.time, m_animationTime); it.mesh->draw(); break; } if (active_shader == m_waterPipeline->m_bridgeShader) { if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; } active_shader->setUniform(m_waterPipeline->m_bridgeUniforms.mvp, it.mvp); active_shader->setUniform(m_waterPipeline->m_bridgeUniforms.model, it.model); active_shader->setUniform(m_waterPipeline->m_bridgeUniforms.color, it.color); QVector3D const light_dir(0.35F, 0.8F, 0.45F); active_shader->setUniform( m_waterPipeline->m_bridgeUniforms.light_direction, light_dir); it.mesh->draw(); break; } if (active_shader == m_waterPipeline->m_road_shader) { if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; } active_shader->setUniform(m_waterPipeline->m_road_uniforms.mvp, it.mvp); active_shader->setUniform(m_waterPipeline->m_road_uniforms.model, it.model); active_shader->setUniform(m_waterPipeline->m_road_uniforms.color, it.color); active_shader->setUniform(m_waterPipeline->m_road_uniforms.alpha, it.alpha); QVector3D const road_light_dir(0.35F, 0.8F, 0.45F); active_shader->setUniform( m_waterPipeline->m_road_uniforms.light_direction, road_light_dir); it.mesh->draw(); break; } auto *uniforms = m_characterPipeline ? m_characterPipeline->resolveUniforms(active_shader) : nullptr; if (uniforms == nullptr) { break; } if (m_lastBoundShader != active_shader) { active_shader->use(); m_lastBoundShader = active_shader; } active_shader->setUniform(uniforms->mvp, it.mvp); active_shader->setUniform(uniforms->model, it.model); Texture *tex_to_use = (it.texture != nullptr) ? it.texture : (m_resources ? m_resources->white() : nullptr); if ((tex_to_use != nullptr) && tex_to_use != m_lastBoundTexture) { tex_to_use->bind(0); m_lastBoundTexture = tex_to_use; active_shader->setUniform(uniforms->texture, 0); } active_shader->setUniform(uniforms->useTexture, it.texture != nullptr); active_shader->setUniform(uniforms->color, it.color); active_shader->setUniform(uniforms->alpha, it.alpha); active_shader->setUniform(uniforms->materialId, it.materialId); it.mesh->draw(); break; } case GridCmdIndex: { if (m_effectsPipeline->m_gridShader == nullptr) { break; } const auto &gc = std::get(cmd); if (m_lastBoundShader != m_effectsPipeline->m_gridShader) { m_effectsPipeline->m_gridShader->use(); m_lastBoundShader = m_effectsPipeline->m_gridShader; } m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.mvp, gc.mvp); m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.model, gc.model); m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.gridColor, gc.color); m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.lineColor, k_grid_line_color); m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.cellSize, gc.cellSize); m_effectsPipeline->m_gridShader->setUniform( m_effectsPipeline->m_gridUniforms.thickness, gc.thickness); if (m_resources) { if (auto *plane = m_resources->ground()) { plane->draw(); } } break; } case SelectionRingCmdIndex: { const auto &sc = std::get(cmd); Mesh *ring = Render::Geom::SelectionRing::get(); if (ring == nullptr) { break; } if (m_lastBoundShader != m_effectsPipeline->m_basicShader) { m_effectsPipeline->m_basicShader->use(); m_lastBoundShader = m_effectsPipeline->m_basicShader; } m_effectsPipeline->m_basicShader->use(); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.useTexture, false); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.color, sc.color); DepthMaskScope const depth_mask(false); PolygonOffsetScope const poly(-1.0F, -1.0F); BlendScope const blend(true); { QMatrix4x4 m = sc.model; m.scale(1.08F, 1.0F, 1.08F); const QMatrix4x4 mvp = view_proj * m; m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.mvp, mvp); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.model, m); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.alpha, sc.alphaOuter); ring->draw(); } { const QMatrix4x4 mvp = view_proj * sc.model; m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.mvp, mvp); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.model, sc.model); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.alpha, sc.alphaInner); ring->draw(); } break; } case SelectionSmokeCmdIndex: { const auto &sm = std::get(cmd); Mesh *disc = Render::Geom::SelectionDisc::get(); if (disc == nullptr) { break; } if (m_lastBoundShader != m_effectsPipeline->m_basicShader) { m_effectsPipeline->m_basicShader->use(); m_lastBoundShader = m_effectsPipeline->m_basicShader; } m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.useTexture, false); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.color, sm.color); DepthMaskScope const depth_mask(false); DepthTestScope const depth_test(true); PolygonOffsetScope const poly(-1.0F, -1.0F); BlendScope const blend(true); for (int i = 0; i < 7; ++i) { float const scale = 1.35F + 0.12F * i; float const a = sm.baseAlpha * (1.0F - 0.09F * i); QMatrix4x4 m = sm.model; m.translate(0.0F, 0.02F, 0.0F); m.scale(scale, 1.0F, scale); const QMatrix4x4 mvp = view_proj * m; m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.mvp, mvp); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.model, m); m_effectsPipeline->m_basicShader->setUniform( m_effectsPipeline->m_basicUniforms.alpha, a); disc->draw(); } break; } case PrimitiveBatchCmdIndex: { const auto &batch = std::get(cmd); if (batch.instanceCount() == 0 || m_primitiveBatchPipeline == nullptr || !m_primitiveBatchPipeline->isInitialized()) { break; } const auto *data = batch.instanceData(); switch (batch.type) { case PrimitiveType::Sphere: m_primitiveBatchPipeline->uploadSphereInstances(data, batch.instanceCount()); m_primitiveBatchPipeline->drawSpheres(batch.instanceCount(), view_proj); break; case PrimitiveType::Cylinder: m_primitiveBatchPipeline->uploadCylinderInstances( data, batch.instanceCount()); m_primitiveBatchPipeline->drawCylinders(batch.instanceCount(), view_proj); break; case PrimitiveType::Cone: m_primitiveBatchPipeline->uploadConeInstances(data, batch.instanceCount()); m_primitiveBatchPipeline->drawCones(batch.instanceCount(), view_proj); break; } m_lastBoundShader = m_primitiveBatchPipeline->shader(); break; } default: break; } ++i; } if (m_lastBoundShader != nullptr) { m_lastBoundShader->release(); m_lastBoundShader = nullptr; } } } // namespace Render::GL