// 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 "scene/components/scene/SubSceneController.hpp" #if DAWN_DEBUG_BUILD #include "scene/debug/SceneDebugLine.hpp" #endif using namespace Dawn; RenderPipeline::RenderPipeline(RenderManager *renderManager) { assertNotNull(renderManager, "RenderPipeline::RenderPipeline: RenderManager cannot be null"); this->renderManager = renderManager; } void RenderPipeline::init() { this->shaderBuffer.init(); } void RenderPipeline::render() { if(this->renderManager->game->scene != nullptr) { this->renderScene(this->renderManager->game->scene); } } void RenderPipeline::renderScene(Scene *scene) { assertNotNull(scene, "RenderPipeline::renderScene: Scene cannot be null"); // Render subscenes first. auto subSceneControllers = scene->findComponents<SubSceneController>(); auto itSubScene = subSceneControllers.begin(); while(itSubScene != subSceneControllers.end()) { Scene *subScene = (Scene *)((*itSubScene)->subScene); 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>(); 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 == nullptr) { ++it; continue; } else 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) { std::vector<struct ShaderPassItem>::iterator itPassItem; assertNotNull(scene, "RenderPipeline::renderSceneCamera: Scene cannot be null"); assertNotNull(camera, "RenderPipeline::renderSceneCamera: Camera cannot be null"); // 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 render target. auto renderTarget = camera->getRenderTarget(); assertNotNull(renderTarget, "RenderPipeline::renderSceneCamera: Camera must have a render target"); // Update shader parameter buffers with current knowledge struct RenderPipelineShaderBufferData shaderBufferData; shaderBufferData.projection = camera->getProjection(); shaderBufferData.view = camera->transform->getWorldTransform(); this->shaderBuffer.buffer(&shaderBufferData); // Prepare a render context. This is just a nice way of letting renderables // know a bit about what is happening, they can choose to use this data to // render themselves differently. struct IRenderableContext context = { scene, camera, this }; // Get the list of things to render first. std::vector<struct ShaderPassItem> shaderPassItems; // Renderables auto renderables = scene->findComponents<IRenderable>(); auto itRenderables = renderables.begin(); while(itRenderables != renderables.end()) { vectorAppend(&shaderPassItems, (*itRenderables)->getRenderPasses(context)); ++itRenderables; } // Debug Lines #if DAWN_DEBUG_BUILD Mesh lineMesh; if(scene->debugLines.size() > 0) { int32_t lineIndex = 0; lineMesh.createBuffers( scene->debugLines.size() * SCENE_DEBUG_LINE_VERTICE_COUNT, scene->debugLines.size() * SCENE_DEBUG_LINE_INDICE_COUNT ); auto itDebugLine = scene->debugLines.begin(); while(itDebugLine != scene->debugLines.end()) { auto item = itDebugLine->createShaderItem( &lineMesh, &lineIndex, camera, this->renderManager->simpleTexturedShader ); shaderPassItems.push_back(item); itDebugLine = scene->debugLines.erase(itDebugLine); lineIndex++; } } #endif // Now we've queued everything, let's sort the rendering queue by the priority std::sort( shaderPassItems.begin(), shaderPassItems.end(), [](struct ShaderPassItem &a, struct ShaderPassItem &b) { if(a.priority == b.priority) { return a.w < b.w; } return a.priority < b.priority; } ); // Now we've sorted everything! Let's actually start rendering. Shader *boundShader = nullptr; std::map<textureslot_t, Texture*> boundTextures; std::map<shaderbufferlocation_t, shaderbufferslot_t> boundBuffers; shaderbufferslot_t slot; // TODO: This will be editable! renderTarget->bind(); renderTarget->clear( RENDER_TARGET_CLEAR_FLAG_DEPTH | RENDER_TARGET_CLEAR_FLAG_COLOR ); // Shader items itPassItem = shaderPassItems.begin(); while(itPassItem != shaderPassItems.end()) { auto item = *itPassItem; // Bind the program. if(boundShader != item.shader) { boundShader = item.shader; boundShader->bind(); } // Bind the textures to the slots auto itTextureSlot = item.textureSlots.begin(); while(itTextureSlot != item.textureSlots.end()) { // Assert texture isn't null, just don't include it. assertNotNull(itTextureSlot->second, "RenderPipeline::renderSceneCamera: Texture cannot be null (omit texture instead)"); if(boundTextures[itTextureSlot->first] != itTextureSlot->second) { itTextureSlot->second->bind(itTextureSlot->first); boundTextures[itTextureSlot->first] = itTextureSlot->second; } ++itTextureSlot; } // Bind the buffers to their slots slot = 0; auto itBufferSlot = item.parameterBuffers.begin(); while(itBufferSlot != item.parameterBuffers.end()) { auto location = itBufferSlot->first; auto buff = itBufferSlot->second; boundBuffers[itBufferSlot->first] = slot; buff->bind(slot); slot++; ++itBufferSlot; } // Now set each of the parameters. Nothing exciting here. auto itColors = item.colorValues.begin(); while(itColors != item.colorValues.end()) { item.shader->setColor(itColors->first, itColors->second); ++itColors; } auto itBool = item.boolValues.begin(); while(itBool != item.boolValues.end()) { item.shader->setBoolean(itBool->first, itBool->second); ++itBool; } auto itMat = item.matrixValues.begin(); while(itMat != item.matrixValues.end()) { item.shader->setMatrix(itMat->first, itMat->second); ++itMat; } auto itVec3 = item.vec3Values.begin(); while(itVec3 != item.vec3Values.end()) { item.shader->setVector3(itVec3->first, itVec3->second); ++itVec3; } auto itText = item.textureValues.begin(); while(itText != item.textureValues.end()) { item.shader->setTexture(itText->first, itText->second); ++itText; } auto itBuffer = item.parameterBuffers.begin(); while(itBuffer != item.parameterBuffers.end()) { item.shader->setParameterBuffer(itBuffer->first, boundBuffers[itBuffer->first]); ++itBuffer; } auto itFloat = item.floatValues.begin(); while(itFloat != item.floatValues.end()) { item.shader->setFloat(itFloat->first, itFloat->second); ++itFloat; } // Set Render flags this->renderManager->setRenderFlags(item.renderFlags); // Thank god that's done, now just draw the damn mesh. item.mesh->draw(item.drawMode, item.start, item.count); ++itPassItem; } } RenderPipeline::~RenderPipeline() { }