diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 1cfa8a3..de8131b 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1192,54 +1192,18 @@ function template_preprocess_maintenance_task_list(&$variables) {
  *
  * See the @link themeable Default theme implementations topic @endlink for
  * details.
+ *
+ * @deprecated in Drupal 8.3.x, will be removed before Drupal 9.0.0.
+ * Now ThemeManager is responsible for setting the default template variables.
+ * There is no publicly usable replacement for this function. It should not be
+ * called anywhere. If you do please stop! Use preprocess hooks to alter default
+ * variables or use your own ThemeManager class.
  */
 function template_preprocess(&$variables, $hook, $info) {
-  // Merge in variables that don't depend on hook and don't change during a
-  // single page request.
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['default_variables'] = &drupal_static(__FUNCTION__);
-  }
-  $default_variables = &$drupal_static_fast['default_variables'];
-  if (!isset($default_variables)) {
-    $default_variables = _template_preprocess_default_variables();
-  }
-  $variables += $default_variables;
-
-  // When theming a render element, merge its #attributes into
-  // $variables['attributes'].
-  if (isset($info['render element'])) {
-    $key = $info['render element'];
-    if (isset($variables[$key]['#attributes'])) {
-      $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
-    }
-  }
-}
-
-/**
- * Returns hook-independent variables to template_preprocess().
- */
-function _template_preprocess_default_variables() {
-  // Variables that don't depend on a database connection.
-  $variables = array(
-    'attributes' => array(),
-    'title_attributes' => array(),
-    'content_attributes' => array(),
-    'title_prefix' => array(),
-    'title_suffix' => array(),
-    'db_is_active' => !defined('MAINTENANCE_MODE'),
-    'is_admin' => FALSE,
-    'logged_in' => FALSE,
-  );
-
-  // Give modules a chance to alter the default template variables.
-  \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $variables);
-
-  // Tell all templates where they are located.
-  $variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
+  $render_element_type = isset($info['render element']) ? $info['render element'] : NULL;
 
-  return $variables;
+  $themeManager = \Drupal::service('theme.manager');
+  $variables += $themeManager->getDefaultTemplateVariables($render_element_type);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index 322761e..cd9fd78 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -112,8 +112,10 @@
  * theme engine name). Modules, themes, and theme engines can provide these
  * functions to modify how the data is preprocessed, before it is passed to the
  * theme template:
- * - template_preprocess(&$variables, $hook): Creates a default set of variables
- *   for all theme hooks with template implementations. Provided by Drupal Core.
+ * - ThemeManager::addDefaultTemplateVariables(&$variables): Creates
+ *   a default set of variables for all theme hooks with template
+ *   implementations. You can alter these variables by
+ *   using HOOK_template_preprocess_default_variables_alter(&$variables).
  * - template_preprocess_HOOK(&$variables): Should be implemented by the module
  *   that registers the theme hook, to set up default variables.
  * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all
@@ -1115,7 +1117,7 @@ function hook_page_bottom(array &$page_bottom) {
  *   - render element: Used for render element items only: the name of the
  *     renderable element or element tree to pass to the theme function. This
  *     name is used as the name of the variable that holds the renderable
- *     element or tree in preprocess and process functions.
+ *     element or tree in preprocess functions.
  *   - file: The file the implementation resides in. This file will be included
  *     prior to the theme being rendered, to make sure that the function or
  *     preprocess function (as needed) is actually loaded.
@@ -1224,10 +1226,9 @@ function hook_theme($existing, $type, $theme, $path) {
  *     'content' => NULL,
  *   ),
  *   'preprocess functions' => array (
- *     0 => 'template_preprocess',
- *     1 => 'template_preprocess_block_content_add_list',
- *     2 => 'contextual_preprocess',
- *     3 => 'seven_preprocess_block_content_add_list',
+ *     0 => 'template_preprocess_block_content_add_list',
+ *     1 => 'contextual_preprocess',
+ *     2 => 'seven_preprocess_block_content_add_list',
  *   ),
  * );
  * @endcode
@@ -1248,29 +1249,27 @@ function hook_theme_registry_alter(&$theme_registry) {
 }
 
 /**
- * Alter the default, hook-independent variables for all templates.
+ * Alter the default variables for all templates.
  *
  * Allows modules to provide additional default template variables or manipulate
- * existing. This hook is invoked from template_preprocess() after basic default
- * template variables have been set up and before the next template preprocess
- * function is invoked.
+ * existing. This hook is invoked from ThemeManager service's
+ * getDefaultTemplateVariables() method after basic default template variables
+ * have been set up and before the template preprocess functions are invoked.
  *
  * Note that the default template variables are statically cached within a
  * request. When adding a template variable that depends on other context, it is
  * your responsibility to appropriately reset the static cache in
- * template_preprocess() when needed:
+ * ThemeManager's getDefaultTemplateVariables() when needed:
  * @code
- * drupal_static_reset('template_preprocess');
+ * drupal_static_reset('getDefaultTemplateVariables');
  * @endcode
  *
  * See user_template_preprocess_default_variables_alter() for an example.
  *
  * @param array $variables
  *   An associative array of default template variables, as set up by
- *   _template_preprocess_default_variables(). Passed by reference.
- *
- * @see template_preprocess()
- * @see _template_preprocess_default_variables()
+ *   Drupal/Core/Theme/ThemeManagerInterface::getDefaultTemplateVariables().
+ *   Passed by reference.
  */
 function hook_template_preprocess_default_variables_alter(&$variables) {
   $variables['is_admin'] = \Drupal::currentUser()->hasPermission('access administration pages');
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 6f5cb1e..a8a0ae0 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -511,9 +511,12 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
         if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
           $info['preprocess functions'] = array();
           $prefixes = array();
+
           if ($type == 'module') {
-            // Default variable preprocessor prefix.
-            $prefixes[] = 'template';
+            // Add template_preprocess_HOOK functions.
+            if (function_exists('template_preprocess_' . $hook)) {
+              $info['preprocess functions'][] = 'template_preprocess_' . $hook;
+            }
             // Add all modules so they can intervene with their own variable
             // preprocessors. This allows them to provide variable preprocessors
             // even if they are not the owner of the current hook.
@@ -609,7 +612,7 @@ protected function completeSuggestion($hook, array &$cache) {
       // @see https://www.drupal.org/node/2457295
       if (isset($cache[$previous_hook]) && !isset($cache[$previous_hook]['incomplete preprocess functions'])) {
         $cache[$hook] = $incomplete_previous_hook + $cache[$previous_hook];
-        if (isset($incomplete_previous_hook['preprocess functions'])) {
+        if (isset($incomplete_previous_hook['preprocess functions']) && isset($cache[$previous_hook]['preprocess functions'])) {
           $diff = array_diff($incomplete_previous_hook['preprocess functions'], $cache[$previous_hook]['preprocess functions']);
           $cache[$hook]['preprocess functions'] = array_merge($cache[$previous_hook]['preprocess functions'], $diff);
         }
diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index ed18039..7bf0e30 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Routing\StackedRouteMatchInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Template\Attribute;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * Provides the default implementation of a theme manager.
@@ -281,6 +282,15 @@ public function render($hook, array $variables) {
         $theme_hook_suggestion = $hook;
       }
     }
+
+    // Set default variables before preprocess hooks.
+    $render_element_type = isset($info['render element']) ? $info['render element'] : NULL;
+    $variables += $this->getDefaultTemplateVariables($render_element_type);
+
+    // Invoke preprocess hooks.
+    // By default $info['preprocess functions'] should always be set, but it's
+    // good to check it if default Registry service implementation is
+    // overridden. See \Drupal\Core\Theme\Registry.
     if (isset($info['preprocess functions'])) {
       foreach ($info['preprocess functions'] as $preprocessor_function) {
         if (function_exists($preprocessor_function)) {
@@ -335,22 +345,6 @@ public function render($hook, array $variables) {
           }
         }
       }
-
-      // In some cases, a template implementation may not have had
-      // template_preprocess() run (for example, if the default implementation
-      // is a function, but a template overrides that default implementation).
-      // In these cases, a template should still be able to expect to have
-      // access to the variables provided by template_preprocess(), so we add
-      // them here if they don't already exist. We don't want the overhead of
-      // running template_preprocess() twice, so we use the 'directory' variable
-      // to determine if it has already run, which while not completely
-      // intuitive, is reasonably safe, and allows us to save on the overhead of
-      // adding some new variable to track that.
-      if (!isset($variables['directory'])) {
-        $default_template_variables = array();
-        template_preprocess($default_template_variables, $hook, $info);
-        $variables += $default_template_variables;
-      }
       if (!isset($default_attributes)) {
         $default_attributes = new Attribute();
       }
@@ -462,4 +456,42 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     $this->alterForTheme($theme, $type, $data, $context1, $context2);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultTemplateVariables($render_element_type) {
+    static $drupal_static_fast;
+    if (!isset($drupal_static_fast)) {
+      $drupal_static_fast['default_variables'] = &drupal_static('getDefaultTemplateVariables');
+    }
+    $default_variables = &$drupal_static_fast['default_variables'];
+    if(!isset($default_variables)) {
+      // Variables that don't depend on a database connection.
+      $default_variables = array(
+        'attributes' => array(),
+        'title_attributes' => array(),
+        'content_attributes' => array(),
+        'title_prefix' => array(),
+        'title_suffix' => array(),
+        'db_is_active' => !defined('MAINTENANCE_MODE'),
+        'is_admin' => FALSE,
+        'logged_in' => FALSE,
+      );
+
+      // Give modules a chance to alter default template variables.
+      \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $default_variables);
+      // Tell all templates where they are located.
+      $default_variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
+    }
+    // Merge element #attributes into $default_variables['attributes'] if they
+    // exist in render_element.
+    if (isset($default_variables[$render_element_type]['#attributes'])) {
+      $default_variables['attributes'] = NestedArray::mergeDeep(
+        $default_variables['attributes'],
+        $default_variables[$render_element_type]['#attributes']
+      );
+    }
+    return $default_variables;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
index 2af8074..74bf3e8 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
@@ -140,4 +140,26 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL);
    */
   public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL);
 
+  /**
+   * Returns default template variables.
+   *
+   * These are set for every template before template preprocessing hooks.
+   * Template preprocess hooks have the following form:
+   * template_preprocess_HOOK($variables).
+   *
+   * This function isn't probably needed in Drupal 9 when the deprecated
+   * template_preprocess function is removed.
+   *
+   * See the @link themeable Default theme implementations topic @endlink for
+   * details.
+   *
+   * @see template_preprocess()
+   *
+   * @param string $render_element_type
+   *   The type of the element to render. E.g. 'html', 'page', 'element',
+   *   'elements, 'form'...
+   * @return
+   *   An array of default template variables.
+   */
+  public function getDefaultTemplateVariables($render_element_type);
 }
diff --git a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
index 4c1459b..592d2be 100644
--- a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
+++ b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
@@ -8,8 +8,6 @@
  *   - source: The source of the translation.
  *   - translation: The translation for the target language.
  *
- * @see template_preprocess()
- *
  * @ingroup themeable
  */
 #}
diff --git a/core/modules/link/templates/link-formatter-link-separate.html.twig b/core/modules/link/templates/link-formatter-link-separate.html.twig
index 469cd9a..5e31645 100644
--- a/core/modules/link/templates/link-formatter-link-separate.html.twig
+++ b/core/modules/link/templates/link-formatter-link-separate.html.twig
@@ -8,7 +8,6 @@
  * - title: (optional) A descriptive or alternate title for the link, which may
  *   be different than the actual link text.
  *
- * @see template_preprocess()
  * @see template_preprocess_link_formatter_link_separate()
  *
  * @ingroup themeable
diff --git a/core/modules/responsive_image/templates/responsive-image.html.twig b/core/modules/responsive_image/templates/responsive-image.html.twig
index 99fca6b..fb7cb41 100644
--- a/core/modules/responsive_image/templates/responsive-image.html.twig
+++ b/core/modules/responsive_image/templates/responsive-image.html.twig
@@ -9,7 +9,6 @@
  * - output_image_tag: Whether or not to output an <img> tag instead of a
  *   <picture> tag.
  *
- * @see template_preprocess()
  * @see template_preprocess_responsive_image()
  *
  * @ingroup themeable
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index afced8e..22c10ea 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -33,8 +33,8 @@ protected function setUp() {
    * Test attribute merging.
    *
    * Render arrays that use a render element and templates (and hence call
-   * template_preprocess()) must ensure the attributes at different occasions
-   * are all merged correctly:
+   * Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables())
+   * must ensure the attributes at different occasions are all merged correctly:
    *   - $variables['attributes'] as passed in to the theme hook implementation.
    *   - the render element's #attributes
    *   - any attributes set in the template's preprocessing function
diff --git a/core/modules/system/templates/dropbutton-wrapper.html.twig b/core/modules/system/templates/dropbutton-wrapper.html.twig
index 5f1aadf..54de2c9 100644
--- a/core/modules/system/templates/dropbutton-wrapper.html.twig
+++ b/core/modules/system/templates/dropbutton-wrapper.html.twig
@@ -6,8 +6,6 @@
  * Available variables:
  * - children: Contains the child elements of the dropbutton menu.
  *
- * @see template_preprocess()
- *
  * @ingroup themeable
  */
 #}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 1e9f539..6ea3362 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -552,7 +552,7 @@ function user_login_finalize(UserInterface $account) {
 function user_user_login($account) {
   // Reset static cache of default variables in template_preprocess() to reflect
   // the new user.
-  drupal_static_reset('template_preprocess');
+  drupal_static_reset('getDefaultTemplateVariables');
 }
 
 /**
@@ -561,7 +561,7 @@ function user_user_login($account) {
 function user_user_logout($account) {
   // Reset static cache of default variables in template_preprocess() to reflect
   // the new user.
-  drupal_static_reset('template_preprocess');
+  drupal_static_reset('getDefaultTemplateVariables');
 }
 
 /**
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
index 257c61e..e5b359e 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
@@ -76,7 +76,6 @@ public function testMultipleSubThemes() {
 
     $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
       'test_subtheme_preprocess_theme_test_template_test',
       'test_subsubtheme_preprocess_theme_test_template_test',
@@ -84,14 +83,12 @@ public function testMultipleSubThemes() {
 
     $preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
       'test_subtheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
 
     $preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
 
@@ -99,7 +96,7 @@ public function testMultipleSubThemes() {
     $this->assertIdentical([
        'template_preprocess_theme_test_function_suggestions',
        'test_basetheme_preprocess_theme_test_function_suggestions',
-    ], $preprocess_functions, "Theme functions don't have template_preprocess but do have template_preprocess_HOOK");
+    ], $preprocess_functions, "Theme functions have template_preprocess_HOOK.");
   }
 
   /**
@@ -114,7 +111,6 @@ public function testSuggestionPreprocessFunctions() {
 
     $suggestions = ['__kitten', '__flamingo'];
     $expected_preprocess_functions = [
-      'template_preprocess',
       'theme_test_preprocess_theme_test_preprocess_suggestions',
     ];
     $suggestion = '';
@@ -127,7 +123,6 @@ public function testSuggestionPreprocessFunctions() {
     } while ($suggestion = array_shift($suggestions));
 
     $expected_preprocess_functions = [
-      'template_preprocess',
       'theme_test_preprocess_theme_test_preprocess_suggestions',
       'test_theme_preprocess_theme_test_preprocess_suggestions',
       'test_theme_preprocess_theme_test_preprocess_suggestions__kitten',
diff --git a/core/themes/classy/templates/field/link-formatter-link-separate.html.twig b/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
index cb94264..5c8cad5 100644
--- a/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
+++ b/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
@@ -8,7 +8,6 @@
  * - title: (optional) A descriptive or alternate title for the link, which may
  *   be different than the actual link text.
  *
- * @see template_preprocess()
  * @see template_preprocess_link_formatter_link_separate()
  */
 #}
diff --git a/core/themes/classy/templates/form/dropbutton-wrapper.html.twig b/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
index ca68716..6d93580 100644
--- a/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
+++ b/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
@@ -6,7 +6,6 @@
  * Available variables:
  * - children: Contains the child elements of the dropbutton menu.
  *
- * @see template_preprocess()
  */
 #}
 {% if children %}
diff --git a/core/themes/stable/templates/admin/config_translation_manage_form_element.html.twig b/core/themes/stable/templates/admin/config_translation_manage_form_element.html.twig
index e7eecfc..bee1387 100644
--- a/core/themes/stable/templates/admin/config_translation_manage_form_element.html.twig
+++ b/core/themes/stable/templates/admin/config_translation_manage_form_element.html.twig
@@ -8,7 +8,6 @@
  *   - source: The source of the translation.
  *   - translation: The translation for the target language.
  *
- * @see template_preprocess()
  */
 #}
 <div class="translation-set clearfix">
diff --git a/core/themes/stable/templates/field/link-formatter-link-separate.html.twig b/core/themes/stable/templates/field/link-formatter-link-separate.html.twig
index 0aa1b4a..4e7248d 100644
--- a/core/themes/stable/templates/field/link-formatter-link-separate.html.twig
+++ b/core/themes/stable/templates/field/link-formatter-link-separate.html.twig
@@ -8,7 +8,6 @@
  * - title: (optional) A descriptive or alternate title for the link, which may
  *   be different than the actual link text.
  *
- * @see template_preprocess()
  * @see template_preprocess_link_formatter_link_separate()
  */
 #}
diff --git a/core/themes/stable/templates/field/responsive-image.html.twig b/core/themes/stable/templates/field/responsive-image.html.twig
index ae77ffb..a896719 100644
--- a/core/themes/stable/templates/field/responsive-image.html.twig
+++ b/core/themes/stable/templates/field/responsive-image.html.twig
@@ -9,7 +9,6 @@
  * - output_image_tag: Whether or not to output an <img> tag instead of a
  *   <picture> tag.
  *
- * @see template_preprocess()
  * @see template_preprocess_responsive_image()
  */
 #}
diff --git a/core/themes/stable/templates/form/dropbutton-wrapper.html.twig b/core/themes/stable/templates/form/dropbutton-wrapper.html.twig
index ca68716..6d93580 100644
--- a/core/themes/stable/templates/form/dropbutton-wrapper.html.twig
+++ b/core/themes/stable/templates/form/dropbutton-wrapper.html.twig
@@ -6,7 +6,6 @@
  * Available variables:
  * - children: Contains the child elements of the dropbutton menu.
  *
- * @see template_preprocess()
  */
 #}
 {% if children %}
