diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index 858e17e085..a4f1edd9ab 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -246,6 +246,16 @@ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
     return params;
 }
 
+VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
+    const VideoCommon::Shader::Sampler& entry) {
+    return TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
+}
+
+VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
+    const VideoCommon::Shader::Image& entry) {
+    return ImageTypeToSurfaceTarget(entry.GetType());
+}
+
 bool SurfaceParams::IsLayered() const {
     switch (target) {
     case SurfaceTarget::Texture1DArray:
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index 709aa0dc2a..129817ad32 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -45,6 +45,14 @@ public:
     static SurfaceParams CreateForFermiCopySurface(
         const Tegra::Engines::Fermi2D::Regs::Surface& config);
 
+    /// Obtains the texture target from a shader's sampler entry.
+    static VideoCore::Surface::SurfaceTarget ExpectedTarget(
+        const VideoCommon::Shader::Sampler& entry);
+
+    /// Obtains the texture target from a shader's sampler entry.
+    static VideoCore::Surface::SurfaceTarget ExpectedTarget(
+        const VideoCommon::Shader::Image& entry);
+
     std::size_t Hash() const {
         return static_cast<std::size_t>(
             Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 41309ebeae..02d2e91365 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -95,10 +95,16 @@ public:
         std::lock_guard lock{mutex};
         const auto gpu_addr{tic.Address()};
         if (!gpu_addr) {
-            return {};
+            return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
+        }
+
+        const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
+        const auto cache_addr{ToCacheAddr(host_ptr)};
+        if (!cache_addr) {
+            return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
         }
         const auto params{SurfaceParams::CreateForTexture(format_lookup_table, tic, entry)};
-        const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
+        const auto [surface, view] = GetSurface(gpu_addr, cache_addr, params, true, false);
         if (guard_samplers) {
             sampled_textures.push_back(surface);
         }
@@ -110,10 +116,15 @@ public:
         std::lock_guard lock{mutex};
         const auto gpu_addr{tic.Address()};
         if (!gpu_addr) {
-            return {};
+            return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
+        }
+        const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
+        const auto cache_addr{ToCacheAddr(host_ptr)};
+        if (!cache_addr) {
+            return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
         }
         const auto params{SurfaceParams::CreateForImage(format_lookup_table, tic, entry)};
-        const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
+        const auto [surface, view] = GetSurface(gpu_addr, cache_addr, params, true, false);
         if (guard_samplers) {
             sampled_textures.push_back(surface);
         }
@@ -143,11 +154,17 @@ public:
             SetEmptyDepthBuffer();
             return {};
         }
+        const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
+        const auto cache_addr{ToCacheAddr(host_ptr)};
+        if (!cache_addr) {
+            SetEmptyDepthBuffer();
+            return {};
+        }
         const auto depth_params{SurfaceParams::CreateForDepthBuffer(
             system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
             regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
             regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
-        auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true);
+        auto surface_view = GetSurface(gpu_addr, cache_addr, depth_params, preserve_contents, true);
         if (depth_buffer.target)
             depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
         depth_buffer.target = surface_view.first;
@@ -180,8 +197,16 @@ public:
             return {};
         }
 
-        auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
-                                       preserve_contents, true);
+        const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
+        const auto cache_addr{ToCacheAddr(host_ptr)};
+        if (!cache_addr) {
+            SetEmptyColorBuffer(index);
+            return {};
+        }
+
+        auto surface_view =
+            GetSurface(gpu_addr, cache_addr, SurfaceParams::CreateForFramebuffer(system, index),
+                       preserve_contents, true);
         if (render_targets[index].target)
             render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
         render_targets[index].target = surface_view.first;
@@ -230,8 +255,14 @@ public:
         const GPUVAddr src_gpu_addr = src_config.Address();
         const GPUVAddr dst_gpu_addr = dst_config.Address();
         DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
-        std::pair<TSurface, TView> dst_surface = GetSurface(dst_gpu_addr, dst_params, true, false);
-        std::pair<TSurface, TView> src_surface = GetSurface(src_gpu_addr, src_params, true, false);
+        const auto dst_host_ptr{system.GPU().MemoryManager().GetPointer(dst_gpu_addr)};
+        const auto dst_cache_addr{ToCacheAddr(dst_host_ptr)};
+        const auto src_host_ptr{system.GPU().MemoryManager().GetPointer(src_gpu_addr)};
+        const auto src_cache_addr{ToCacheAddr(src_host_ptr)};
+        std::pair<TSurface, TView> dst_surface =
+            GetSurface(dst_gpu_addr, dst_cache_addr, dst_params, true, false);
+        std::pair<TSurface, TView> src_surface =
+            GetSurface(src_gpu_addr, src_cache_addr, src_params, true, false);
         ImageBlit(src_surface.second, dst_surface.second, copy_config);
         dst_surface.first->MarkAsModified(true, Tick());
     }
@@ -347,13 +378,6 @@ protected:
         return new_surface;
     }
 
-    std::pair<TSurface, TView> GetFermiSurface(
-        const Tegra::Engines::Fermi2D::Regs::Surface& config) {
-        SurfaceParams params = SurfaceParams::CreateForFermiCopySurface(config);
-        const GPUVAddr gpu_addr = config.Address();
-        return GetSurface(gpu_addr, params, true, false);
-    }
-
     Core::System& system;
 
 private:
@@ -614,22 +638,9 @@ private:
      *                          left blank.
      * @param is_render         Whether or not the surface is a render target.
      **/
-    std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const SurfaceParams& params,
-                                          bool preserve_contents, bool is_render) {
-        const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
-        const auto cache_addr{ToCacheAddr(host_ptr)};
-
-        // Step 0: guarantee a valid surface
-        if (!cache_addr) {
-            // Return a null surface if it's invalid
-            SurfaceParams new_params = params;
-            new_params.width = 1;
-            new_params.height = 1;
-            new_params.depth = 1;
-            new_params.block_height = 0;
-            new_params.block_depth = 0;
-            return InitializeSurface(gpu_addr, new_params, false);
-        }
+    std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const CacheAddr cache_addr,
+                                          const SurfaceParams& params, bool preserve_contents,
+                                          bool is_render) {
 
         // Step 1
         // Check Level 1 Cache for a fast structural match. If candidate surface
@@ -793,6 +804,41 @@ private:
         }
     }
 
+    /**
+     * Gets a null surface based on a target texture.
+     * @param target The target of the null surface.
+     */
+    TView GetNullSurface(SurfaceTarget target) {
+        const u32 i_target = static_cast<u32>(target);
+        if (const auto it = invalid_cache.find(i_target); it != invalid_cache.end()) {
+            return it->second->GetMainView();
+        }
+        SurfaceParams params{};
+        params.target = target;
+        params.is_tiled = false;
+        params.srgb_conversion = false;
+        params.is_layered = false;
+        params.block_width = 0;
+        params.block_height = 0;
+        params.block_depth = 0;
+        params.tile_width_spacing = 1;
+        params.width = 1;
+        params.height = 1;
+        params.depth = 1;
+        params.pitch = 4;
+        params.num_levels = 1;
+        params.emulated_levels = 1;
+        params.pixel_format = VideoCore::Surface::PixelFormat::RGBA16F;
+        params.type = VideoCore::Surface::SurfaceType::ColorTexture;
+        auto surface = CreateSurface(0ULL, params);
+        invalid_memory.clear();
+        invalid_memory.resize(surface->GetHostSizeInBytes(), 0U);
+        surface->UploadTexture(invalid_memory);
+        surface->MarkAsModified(false, Tick());
+        invalid_cache.emplace(i_target, surface);
+        return surface->GetMainView();
+    }
+
     /**
      * Gets the a source and destination starting address and parameters,
      * and tries to deduce if they are supposed to be depth textures. If so, their
@@ -991,6 +1037,11 @@ private:
 
     std::vector<TSurface> sampled_textures;
 
+    /// This cache stores null surfaces in order to be used as a placeholder
+    /// for invalid texture calls.
+    std::unordered_map<u32, TSurface> invalid_cache;
+    std::vector<u8> invalid_memory;
+
     StagingCache staging_cache;
     std::recursive_mutex mutex;
 };