diff --git a/sources/libengine/graphics/renderPipeline.cpp b/sources/libengine/graphics/renderPipeline.cpp index 34ac628a..da13127d 100644 --- a/sources/libengine/graphics/renderPipeline.cpp +++ b/sources/libengine/graphics/renderPipeline.cpp @@ -111,6 +111,7 @@ namespace cage FontLayoutResult layout; Holder font; Vec3 color; + sint32 renderLayer = 0; }; struct RenderData : private Noncopyable @@ -138,7 +139,7 @@ namespace cage struct CameraData : private Noncopyable { - std::map shadowmaps; + std::map> shadowmaps; uint32 lightsCount = 0; uint32 shadowedLightsCount = 0; }; @@ -580,15 +581,17 @@ namespace cage const auto armature = rm.skeletalAnimation->armature(); for (uint32 i = 0; i < armature.size(); i++) { - RenderData pd; - RenderModel pm; - pm.mesh = modelBone.share(); - pd.model = rd.model * Mat4(armature[i]); - pm.uni = initializeMeshUni(pd.model); - pm.uni.color = Vec4(colorGammaToLinear(colorHsvToRgb(Vec3(Real(i) / Real(armature.size()), 1, 1))), 1); - pm.uni.normalMat.data[2][3] = rm.uni.normalMat.data[2][3]; - pd.data = std::move(pm); - renderData.push_back(std::move(pd)); + RenderData d; + d.model = rd.model * Mat4(armature[i]); + d.e = rd.e; + d.depth = rd.depth; + RenderModel r; + r.mesh = modelBone.share(); + r.uni = initializeMeshUni(d.model); + r.uni.color = Vec4(colorGammaToLinear(colorHsvToRgb(Vec3(Real(i) / Real(armature.size()), 1, 1))), 1); + r.uni.normalMat.data[2][3] = rm.uni.normalMat.data[2][3]; + d.data = std::move(r); + renderData.push_back(std::move(d)); } } @@ -688,10 +691,10 @@ namespace cage ps.reset(); } - rd.translucent = any(rm.mesh->flags & (MeshRenderFlags::Transparent | MeshRenderFlags::Fade)) || rm.render.opacity < 1; rm.uni.normalMat.data[2][3] = any(rm.mesh->flags & MeshRenderFlags::Lighting) ? 1 : 0; // is lighting enabled rm.uni.normalMat.data[1][3] = any(rm.mesh->flags & MeshRenderFlags::Fade) ? 1 : 0; // transparent mode is to fade rd.depth = (rm.uni.mvpMat * Vec4(0, 0, 0, 1))[2]; + rd.translucent = any(rm.mesh->flags & (MeshRenderFlags::Transparent | MeshRenderFlags::Fade)) || rm.render.opacity < 1; if (rm.skeletalAnimation && cnfRenderSkeletonBones) prepareModelBones(rd); @@ -701,8 +704,10 @@ namespace cage void prepareObject(const RenderData &rd, Holder object) { - CAGE_ASSERT(std::holds_alternative(rd.data)); CAGE_ASSERT(object->lodsCount() > 0); + CAGE_ASSERT(std::holds_alternative(rd.data)); + const RenderModel &rm = std::get(rd.data); + CAGE_ASSERT(!rm.mesh); uint32 preferredLod = 0; if (object->lodsCount() > 1) @@ -746,18 +751,50 @@ namespace cage // render selected lod for (auto &it : models) { - RenderModel rm; - rm.mesh = std::move(it); - RenderData pr; - pr.model = rd.model; - pr.depth = rd.depth; - pr.e = rd.e; - pr.translucent = rd.translucent; - pr.data = std::move(rm); - prepareModel(pr, +object); + RenderData d; + d.model = rd.model; + d.e = rd.e; + RenderModel r; + r.uni = rm.uni; + r.frustum = rm.frustum; + r.customShaderData = rm.customShaderData; + r.render = rm.render; + r.textureAnimation = rm.textureAnimation; + r.skeletalAnimation = rm.skeletalAnimation.share(); + r.mesh = std::move(it); + d.data = std::move(r); + prepareModel(d, +object); } } + void prepareText(Entity *e, TextComponent tc) + { + if (!tc.font) + tc.font = detail::GuiTextFontDefault; + if (!tc.font) + tc.font = HashString("cage/font/ubuntu/regular.ttf"); + RenderText rt; + rt.font = assets->get(tc.font); + if (!rt.font) + return; + FontFormat format; + format.size = 1; + format.align = tc.align; + format.lineSpacing = tc.lineSpacing; + const String str = textsGet(tc.textId, tc.value); + rt.layout = rt.font->layout(str, format); + if (rt.layout.glyphs.empty()) + return; + rt.color = colorGammaToLinear(tc.color) * tc.intensity; + rt.renderLayer = tc.renderLayer; + RenderData rd; + rd.model = modelTransform(e) * Mat4(Vec3(rt.layout.size * Vec2(-0.5, 0.5), 0)); + rd.depth = (viewProj * rd.model * Vec4(0, 0, 0, 1))[2]; + rd.translucent = true; + rd.data = std::move(rt); + renderData.push_back(std::move(rd)); + } + void prepareEntities() { entitiesVisitor( @@ -766,8 +803,8 @@ namespace cage if ((rc.sceneMask & camera.sceneMask) == 0) return; RenderData rd; - rd.e = e; rd.model = modelTransform(e); + rd.e = e; RenderModel rm; rm.uni = initializeMeshUni(rd.model); rm.frustum = Frustum(rm.uni.mvpMat); @@ -798,37 +835,41 @@ namespace cage +scene, false); entitiesVisitor( - [&](Entity *e, const TextComponent &tc_) + [&](Entity *e, const TextComponent &tc) { - if ((tc_.sceneMask & camera.sceneMask) == 0) - return; - TextComponent tc = tc_; - RenderText rt; - if (!tc.font) - tc.font = detail::GuiTextFontDefault; - if (!tc.font) - tc.font = HashString("cage/font/ubuntu/regular.ttf"); - rt.font = assets->get(tc.font); - if (!rt.font) + if ((tc.sceneMask & camera.sceneMask) == 0) return; - FontFormat format; - format.size = 1; - format.align = tc.align; - format.lineSpacing = tc.lineSpacing; - const String str = textsGet(tc.textId, tc.value); - rt.layout = rt.font->layout(str, format); - if (rt.layout.glyphs.empty()) - return; - rt.color = colorGammaToLinear(tc.color) * tc.intensity; - RenderData rd; - rd.model = modelTransform(e) * Mat4(Vec3(rt.layout.size * Vec2(-0.5, 0.5), 0)); - rd.data = std::move(rt); - rd.translucent = true; - renderData.push_back(std::move(rd)); + prepareText(e, tc); }, +scene, false); } + void sortRenderData() + { + std::sort(renderData.begin(), renderData.end(), + [](const RenderData &a, const RenderData &b) -> bool + { + const auto &cmp = [](const RenderData &d) + { + // reduce switching shaders, then depth test/writes and other states, then meshes + const auto tmp = std::visit( + [](const auto &r) -> std::tuple + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return { r.renderLayer, 0, MeshRenderFlags(), (Model *)nullptr, false }; + if constexpr (std::is_same_v) + return { r.render.renderLayer + r.mesh->layer, r.mesh->shaderName, r.mesh->flags, +r.mesh, !!r.skeletalAnimation }; + return {}; + }, + d.data); + // opaque first, translucent last; translucent is ordered back-to-front, opaque is ordered front-to-back + return std::tuple{ d.translucent, -d.depth * d.translucent, tmp, d.depth * !d.translucent }; + }; + return cmp(a) < cmp(b); + }); + } + void taskShadowmap() { CAGE_ASSERT(!data); @@ -850,6 +891,7 @@ namespace cage renderQueue->checkGlErrorDebug(); prepareEntities(); + sortRenderData(); renderPass(RenderModeEnum::Shadowmap); renderQueue->resetFrameBuffer(); @@ -892,8 +934,8 @@ namespace cage texCube.reserve(CAGE_SHADER_MAX_SHADOWMAPSCUBE); for (auto &[e, sh_] : data->shadowmaps) { - CAGE_ASSERT(sh_.shadowmap); - ShadowmapData &sh = *sh_.shadowmap; + CAGE_ASSERT(sh_->shadowmap); + ShadowmapData &sh = *sh_->shadowmap; if (sh.lightComponent.lightType == LightTypeEnum::Point) { if (texCube.size() == CAGE_SHADER_MAX_SHADOWMAPSCUBE) @@ -933,6 +975,7 @@ namespace cage view = inverse(model); viewProj = projection * view; prepareEntities(); + sortRenderData(); prepareCameraLights(); TextureHandle colorTexture = provisionalGraphics->texture(Stringizer() + "colorTarget_" + name + "_" + resolution, @@ -1154,10 +1197,13 @@ namespace cage } } - Holder prepareShadowmap(Entity *e, const LightComponent &lc, const ShadowmapComponent &sc) const + Holder prepareShadowmap(Entity *e, const LightComponent &lc, const ShadowmapComponent &sc) { - Holder data = systemMemory().createHolder(this); CAGE_ASSERT(e->id() != 0); // lights with shadowmap may not be anonymous + + Holder data = systemMemory().createHolder(this); + this->data->shadowmaps[e] = data.share(); + data->name = Stringizer() + name + "_shadowmap_" + e->id(); data->shadowmap = ShadowmapData(); ShadowmapData &shadowmap = *data->shadowmap; @@ -1246,7 +1292,7 @@ namespace cage // ensure that shadowmaps are rendered before the camera for (auto &shm : data->shadowmaps) - queue->enqueue(std::move(shm.second.renderQueue)); + queue->enqueue(std::move(shm.second->renderQueue)); queue->enqueue(std::move(renderQueue)); return queue; @@ -1279,14 +1325,13 @@ namespace cage } // todo list: -// sort draw calls // find consecutive instances -// renderLayer (mesh, RenderComponent, text) // reduce size of RenderModel (remove frustum, possibly some more) // opacity for text // consider parallelizing frustum culling // consider shadows for text // vectors (meshes, armatures, customShaderData) should be reused -// assert std::holds_alternative in prepareObject // interpolated camera transform -// shadowmaps currently broken +// reserve renderData +// shaderanim example: unknown shader variant +// mazetdBuildings example: asset name 0 or m