public NormalMapRenderer MakeNormalMapRenderer(ChannelInputs ldChannelInputs, ChannelInputs hdChannelInputs, UvSet uvSet)
    {
        var ldChannelOutputs = figure.Evaluate(null, ldChannelInputs);
        var hdChannelOutputs = figure.Evaluate(null, hdChannelInputs);

        var ldControlPositions = figure.Geometry.VertexPositions.Select(p => p).ToArray();

        figure.Morpher.Apply(ldChannelOutputs, ldControlPositions);

        var hdControlPositions = figure.Geometry.VertexPositions.Select(p => p).ToArray();

        figure.Morpher.Apply(hdChannelOutputs, hdControlPositions);

        var activeHdMorphs = figure.Morpher.LoadActiveHdMorphs(hdChannelOutputs);

        int maxLevel = activeHdMorphs.Max(morph => morph.Morph.MaxLevel) + ExtraRefinementLevels;

        var controlTopology = new QuadTopology(figure.Geometry.VertexCount, figure.Geometry.Faces);

        var applier = HdMorphApplier.Make(controlTopology, hdControlPositions);

        var controlUvTopology = new QuadTopology(uvSet.Uvs.Length, uvSet.Faces);
        var controlUvs        = uvSet.Uvs;

        var refinement   = new Refinement(controlTopology, maxLevel);
        var uvRefinement = new Refinement(controlUvTopology, maxLevel, BoundaryInterpolation.EdgeAndCorner);

        var topology    = controlTopology;
        var ldPositions = ldControlPositions;
        var hdPositions = hdControlPositions;

        var uvTopology          = controlUvTopology;
        var uvs                 = controlUvs;
        var texturedLdPositions = ExtractTexturedPositions(topology, uvTopology, ldPositions);

        for (int levelIdx = 1; levelIdx <= maxLevel; ++levelIdx)
        {
            topology    = refinement.GetTopology(levelIdx);
            ldPositions = refinement.Refine(levelIdx, ldPositions);
            hdPositions = refinement.Refine(levelIdx, hdPositions);

            foreach (var activeHdMorph in activeHdMorphs)
            {
                applier.Apply(activeHdMorph.Morph, activeHdMorph.Weight, levelIdx, topology, hdPositions);
            }

            uvTopology          = uvRefinement.GetTopology(levelIdx);
            uvs                 = uvRefinement.Refine(levelIdx, uvs);
            texturedLdPositions = uvRefinement.Refine(levelIdx, texturedLdPositions);
        }

        var ldLimit         = refinement.Limit(ldPositions);
        var hdLimit         = refinement.Limit(hdPositions);
        var uvLimit         = uvRefinement.Limit(uvs);
        var texturedLdLimit = uvRefinement.Limit(texturedLdPositions);

        int[] faceMap = refinement.GetFaceMap();

        refinement.Dispose();
        uvRefinement.Dispose();

        var hdNormals  = CalculateNormals(hdLimit);
        var ldNormals  = CalculateNormals(ldLimit);
        var ldTangents = CalculateTangents(uvLimit, texturedLdLimit);

        int[] controlSurfaceMap = figure.Geometry.SurfaceMap;
        int[] surfaceMap        = faceMap
                                  .Select(controlFaceIdx => controlSurfaceMap[controlFaceIdx])
                                  .ToArray();

        var renderer = new NormalMapRenderer(device, shaderCache, hdNormals, ldNormals, topology.Faces, uvLimit.values, ldTangents, uvTopology.Faces, surfaceMap);

        return(renderer);
    }