// 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() { } void RenderPipeline::init(const std::weak_ptr renderManager) { this->renderManager = renderManager; shaderBuffer.init(); } void RenderPipeline::render() { auto rm = renderManager.lock(); assertNotNull(rm, "RenderManager cannot be null"); auto game = rm->game.lock(); assertNotNull(game, "Game cannot be null"); if(game->scene != nullptr) { renderScene(game->scene); } } void RenderPipeline::renderScene(const std::shared_ptr scene) { assertNotNull(scene, "Scene cannot be null"); auto rm = renderManager.lock(); assertNotNull(rm, "RenderManager cannot be null"); // Render subscenes first. auto subSceneControllers = scene->findComponents(); auto itSubScene = subSceneControllers.begin(); while(itSubScene != subSceneControllers.end()) { auto subScene = (*itSubScene)->subScene; if(subScene == nullptr) { ++itSubScene; continue; } if( (*itSubScene)->onlyUpdateUnpaused && scene->game.lock()->timeManager.isPaused ) { ++itSubScene; continue; } renderScene(subScene); ++itSubScene; } // Now render backbuffers. auto backBuffer = rm->getBackBuffer(); auto cameras = scene->findComponents(); std::shared_ptr backBufferCamera; // First, render all non-backbuffer cameras. auto it = cameras.begin(); while(it != cameras.end()) { auto 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 { renderSceneCamera(scene, *it); } ++it; } // Now render the backbuffer camera. if(backBufferCamera == nullptr) return; renderSceneCamera(scene, backBufferCamera); } void RenderPipeline::renderSceneCamera( const std::shared_ptr scene, const std::shared_ptr camera ) { auto rm = renderManager.lock(); assertNotNull(rm, "RenderManager cannot be null"); std::vector::iterator itPassItem; assertNotNull(scene, "Scene cannot be null"); assertNotNull(camera, "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. renderId--; // Get the render target. auto renderTarget = camera->getRenderTarget(); assertNotNull(renderTarget, "Camera must have a render target"); // Update shader parameter buffers with current knowledge struct RenderPipelineShaderBufferData shaderBufferData; shaderBufferData.projection = camera->getProjection(); shaderBufferData.view = camera->item.lock()->getWorldTransform(); 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, shared_from_this() }; // Get the list of things to render first. std::vector shaderPassItems; // Renderables auto renderables = scene->findComponents(); auto itRenderables = renderables.begin(); while(itRenderables != renderables.end()) { vectorAppend(shaderPassItems, (*itRenderables)->getRenderPasses(context)); ++itRenderables; } // Inject index into each item itPassItem = shaderPassItems.begin(); while(itPassItem != shaderPassItems.end()) { itPassItem->index = itPassItem; ++itPassItem; } // 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) { // Compare indexes if w is same. if(a.w == b.w) return a.index < b.index; return a.w < b.w; } return a.priority < b.priority; } ); // Now we've sorted everything! Let's actually start rendering. std::shared_ptr boundShader; std::map boundTextures; std::map 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, "Texture cannot be null (omit)"); 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 rm->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() { }