// Copyright (c) 2022 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "RenderPipeline.hpp" #include "game/DawnGame.hpp" #include "display/RenderManager.hpp" using namespace Dawn; RenderPipeline::RenderPipeline(RenderManager *renderManager) { assertNotNull(renderManager); this->renderManager = renderManager; } void RenderPipeline::init() { } void RenderPipeline::render() { if(this->renderManager->game->scene != nullptr) { this->renderScene(this->renderManager->game->scene); } } void RenderPipeline::renderScene(Scene *scene) { assertNotNull(scene); // Render subscenes first. auto subSceneControllers = scene->findComponents(); auto itSubScene = subSceneControllers.begin(); while(itSubScene != subSceneControllers.end()) { auto subScene = (*itSubScene)->getSubScene(); if(subScene == nullptr) { ++itSubScene; continue; } if((*itSubScene)->onlyUpdateUnpaused && scene->game->timeManager.isPaused) { ++itSubScene; continue; } this->renderScene(subScene); ++itSubScene; } // Now render backbuffers. auto backBuffer = this->renderManager->getBackBuffer(); auto cameras = scene->findComponents(); Camera *backBufferCamera = nullptr; // First, render all non-backbuffer cameras. auto it = cameras.begin(); while(it != cameras.end()) { RenderTarget *cameraTarget = (*it)->getRenderTarget(); // Leave the backbuffer camera(s) to last, so we skip them. we do this so // that children framebuffers contain the CURRENT frame, not LAST frame. if(cameraTarget == backBuffer) { backBufferCamera = *it; } else { this->renderSceneCamera(scene, *it); } ++it; } // Now render the backbuffer camera. if(backBufferCamera == nullptr) return; this->renderSceneCamera(scene, backBufferCamera); } void RenderPipeline::renderSceneCamera(Scene *scene, Camera *camera) { assertNotNull(scene); assertNotNull(camera); // Create a new render ID. Long story short this is a really dirty way of // not sending parameters to shaders more than we need. this->renderId++; // Get the list of things to render first. std::vector pipelineItems; // Meshes auto meshes = scene->findComponents(); auto itMesh = meshes.begin(); while(itMesh != meshes.end()) { // Get Mesh auto mesh = *itMesh; assertNotNull(mesh->mesh); // Make sure this mesh has a material auto mat = mesh->item->getComponent(); assertNotNull(mat); auto shader = mat->getShader(); assertNotNull(shader); // Now do each pass. auto passes = shader->getItemPasses(mesh, mat); auto itPass = passes.begin(); while(itPass != passes.end()) { struct RenderPipelineItem item; item.mesh = mesh; item.pass = *itPass; // Do we need to get the W Vector? if(item.pass.needsW) { assertUnreachable();// TODO: Add distance from camera for W vector. } else { item.w = 0; } // Queue pipelineItems.push_back(item); ++itPass; } // Now, for optimization, we bind the global parameters here, once for each // shader. if(shader->renderId != this->renderId) { shader->setGlobalParameters( camera->projection, camera->transform->getWorldTransform() ); shader->renderId = this->renderId; } ++itMesh; } // TODO: Get UI stuff here. // Now we've queued everything, let's sort the rendering queue by the priority std::sort( pipelineItems.begin(), pipelineItems.end(), [](struct RenderPipelineItem &a, struct RenderPipelineItem &b){ if(a.pass.orderShader == b.pass.orderShader) { return a.w < b.w; } return a.pass.orderShader < b.pass.orderShader; } ); // Now we've sorted everything! Let's actually start rendering. ShaderProgram *boundProgram = nullptr; std::map boundTextures; auto renderTarget = camera->getRenderTarget(); assertNotNull(renderTarget); // TODO: This will be editable! renderTarget->bind(); renderTarget->clear( RENDER_TARGET_CLEAR_FLAG_DEPTH | RENDER_TARGET_CLEAR_FLAG_COLOR ); this->renderManager->setRenderFlags( RENDER_MANAGER_RENDER_FLAG_DEPTH_TEST | RENDER_MANAGER_RENDER_FLAG_BLEND ); auto itItems = pipelineItems.begin(); while(itItems != pipelineItems.end()) { auto item = *itItems; // Bind the program. if(boundProgram != item.pass.shaderProgram) { boundProgram->bind(); boundProgram = item.pass.shaderProgram; } // Bind the textures to the slots auto itTextureSlot = item.pass.textureSlots.begin(); while(itTextureSlot != item.pass.textureSlots.end()) { if(boundTextures[itTextureSlot->first] != itTextureSlot->second) { itTextureSlot->second->bind(itTextureSlot->first); boundTextures[itTextureSlot->first] = itTextureSlot->second; } ++itTextureSlot; } // Now set each of the parameters. Nothing exciting here. auto itColors = item.pass.colorValues.begin(); while(itColors != item.pass.colorValues.end()) { item.pass.shaderProgram->setColor(itColors->first, itColors->second); ++itColors; } auto itBool = item.pass.boolValues.begin(); while(itBool != item.pass.boolValues.end()) { item.pass.shaderProgram->setBoolean(itBool->first, itBool->second); ++itBool; } auto itMat = item.pass.matrixValues.begin(); while(itMat != item.pass.matrixValues.end()) { item.pass.shaderProgram->setMatrix(itMat->first, itMat->second); ++itMat; } auto itVec3 = item.pass.vec3Values.begin(); while(itVec3 != item.pass.vec3Values.end()) { item.pass.shaderProgram->setVector3(itVec3->first, itVec3->second); ++itVec3; } auto itText = item.pass.textureValues.begin(); while(itText != item.pass.textureValues.end()) { item.pass.shaderProgram->setTexture(itText->first, itText->second); ++itText; } auto itFloat = item.pass.floatValues.begin(); while(itFloat != item.pass.floatValues.end()) { item.pass.shaderProgram->setFloat(itFloat->first, itFloat->second); ++itFloat; } // Thank god that's done, now just draw the damn mesh. item.mesh->mesh->draw(MESH_DRAW_MODE_TRIANGLES, 0, -1); ++itItems; } } RenderPipeline::~RenderPipeline() { }