From c5047540c9c3f807ed2164b938898ffff27f53bc Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Tue, 2 Apr 2019 15:54:11 -0300
Subject: [PATCH 1/2] video_core: Abstract vk_sampler_cache into a templated
 class

---
 src/video_core/CMakeLists.txt                 |  2 +
 .../renderer_vulkan/vk_sampler_cache.cpp      | 40 ++++---------
 .../renderer_vulkan/vk_sampler_cache.h        | 36 ++---------
 src/video_core/sampler_cache.cpp              | 21 +++++++
 src/video_core/sampler_cache.h                | 60 +++++++++++++++++++
 5 files changed, 101 insertions(+), 58 deletions(-)
 create mode 100644 src/video_core/sampler_cache.cpp
 create mode 100644 src/video_core/sampler_cache.h

diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 14b76680f6..1a0cc6df15 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -67,6 +67,8 @@ add_library(video_core STATIC
     renderer_opengl/renderer_opengl.h
     renderer_opengl/utils.cpp
     renderer_opengl/utils.h
+    sampler_cache.cpp
+    sampler_cache.h
     shader/decode/arithmetic.cpp
     shader/decode/arithmetic_immediate.cpp
     shader/decode/bfe.cpp
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
index ed3178f095..801826d3d9 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -7,7 +7,6 @@
 #include <unordered_map>
 
 #include "common/assert.h"
-#include "common/cityhash.h"
 #include "video_core/renderer_vulkan/declarations.h"
 #include "video_core/renderer_vulkan/maxwell_to_vk.h"
 #include "video_core/renderer_vulkan/vk_sampler_cache.h"
@@ -28,39 +27,20 @@ static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4>
     }
 }
 
-std::size_t SamplerCacheKey::Hash() const {
-    static_assert(sizeof(raw) % sizeof(u64) == 0);
-    return static_cast<std::size_t>(
-        Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
-}
-
-bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
-    return raw == rhs.raw;
-}
-
 VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
 
 VKSamplerCache::~VKSamplerCache() = default;
 
-vk::Sampler VKSamplerCache::GetSampler(const Tegra::Texture::TSCEntry& tsc) {
-    const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
-    auto& sampler = entry->second;
-    if (is_cache_miss) {
-        sampler = CreateSampler(tsc);
-    }
-    return *sampler;
-}
+UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
+    const float max_anisotropy{tsc.GetMaxAnisotropy()};
+    const bool has_anisotropy{max_anisotropy > 1.0f};
 
-UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) {
-    const float max_anisotropy = tsc.GetMaxAnisotropy();
-    const bool has_anisotropy = max_anisotropy > 1.0f;
-
-    const auto border_color = tsc.GetBorderColor();
-    const auto vk_border_color = TryConvertBorderColor(border_color);
+    const auto border_color{tsc.GetBorderColor()};
+    const auto vk_border_color{TryConvertBorderColor(border_color)};
     UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
                          border_color[0], border_color[1], border_color[2], border_color[3]);
 
-    constexpr bool unnormalized_coords = false;
+    constexpr bool unnormalized_coords{false};
 
     const vk::SamplerCreateInfo sampler_ci(
         {}, MaxwellToVK::Sampler::Filter(tsc.mag_filter),
@@ -73,9 +53,13 @@ UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc)
         tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack),
         unnormalized_coords);
 
-    const auto& dld = device.GetDispatchLoader();
-    const auto dev = device.GetLogical();
+    const auto& dld{device.GetDispatchLoader()};
+    const auto dev{device.GetLogical()};
     return dev.createSamplerUnique(sampler_ci, nullptr, dld);
 }
 
+vk::Sampler VKSamplerCache::ToSamplerType(const UniqueSampler& sampler) const {
+    return *sampler;
+}
+
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h
index c6394dc877..771b05c739 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.h
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h
@@ -8,49 +8,25 @@
 
 #include "common/common_types.h"
 #include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/sampler_cache.h"
 #include "video_core/textures/texture.h"
 
 namespace Vulkan {
 
 class VKDevice;
 
-struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
-    std::size_t Hash() const;
-
-    bool operator==(const SamplerCacheKey& rhs) const;
-
-    bool operator!=(const SamplerCacheKey& rhs) const {
-        return !operator==(rhs);
-    }
-};
-
-} // namespace Vulkan
-
-namespace std {
-
-template <>
-struct hash<Vulkan::SamplerCacheKey> {
-    std::size_t operator()(const Vulkan::SamplerCacheKey& k) const noexcept {
-        return k.Hash();
-    }
-};
-
-} // namespace std
-
-namespace Vulkan {
-
-class VKSamplerCache {
+class VKSamplerCache final : public VideoCommon::SamplerCache<vk::Sampler, UniqueSampler> {
 public:
     explicit VKSamplerCache(const VKDevice& device);
     ~VKSamplerCache();
 
-    vk::Sampler GetSampler(const Tegra::Texture::TSCEntry& tsc);
+protected:
+    UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
+
+    vk::Sampler ToSamplerType(const UniqueSampler& sampler) const;
 
 private:
-    UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc);
-
     const VKDevice& device;
-    std::unordered_map<SamplerCacheKey, UniqueSampler> cache;
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/sampler_cache.cpp b/src/video_core/sampler_cache.cpp
new file mode 100644
index 0000000000..53c7ef12d0
--- /dev/null
+++ b/src/video_core/sampler_cache.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/cityhash.h"
+#include "common/common_types.h"
+#include "video_core/sampler_cache.h"
+
+namespace VideoCommon {
+
+std::size_t SamplerCacheKey::Hash() const {
+    static_assert(sizeof(raw) % sizeof(u64) == 0);
+    return static_cast<std::size_t>(
+        Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
+}
+
+bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
+    return raw == rhs.raw;
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/sampler_cache.h b/src/video_core/sampler_cache.h
new file mode 100644
index 0000000000..cbe3ad0711
--- /dev/null
+++ b/src/video_core/sampler_cache.h
@@ -0,0 +1,60 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <unordered_map>
+
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
+    std::size_t Hash() const;
+
+    bool operator==(const SamplerCacheKey& rhs) const;
+
+    bool operator!=(const SamplerCacheKey& rhs) const {
+        return !operator==(rhs);
+    }
+};
+
+} // namespace VideoCommon
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::SamplerCacheKey> {
+    std::size_t operator()(const VideoCommon::SamplerCacheKey& k) const noexcept {
+        return k.Hash();
+    }
+};
+
+} // namespace std
+
+namespace VideoCommon {
+
+template <typename SamplerType, typename SamplerStorageType>
+class SamplerCache {
+public:
+    SamplerType GetSampler(const Tegra::Texture::TSCEntry& tsc) {
+        const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
+        auto& sampler = entry->second;
+        if (is_cache_miss) {
+            sampler = CreateSampler(tsc);
+        }
+        return ToSamplerType(sampler);
+    }
+
+protected:
+    virtual SamplerStorageType CreateSampler(const Tegra::Texture::TSCEntry& tsc) const = 0;
+
+    virtual SamplerType ToSamplerType(const SamplerStorageType& sampler) const = 0;
+
+private:
+    std::unordered_map<SamplerCacheKey, SamplerStorageType> cache;
+};
+
+} // namespace VideoCommon
\ No newline at end of file

From 576ad9a012a22615ad430dd898b47a8d59b9ee3f Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Tue, 2 Apr 2019 16:58:08 -0300
Subject: [PATCH 2/2] gl_sampler_cache: Port sampler cache to OpenGL

---
 src/video_core/CMakeLists.txt                 |  2 +
 .../renderer_opengl/gl_rasterizer.cpp         | 94 +------------------
 .../renderer_opengl/gl_rasterizer.h           | 32 +------
 .../renderer_opengl/gl_sampler_cache.cpp      | 52 ++++++++++
 .../renderer_opengl/gl_sampler_cache.h        | 25 +++++
 5 files changed, 82 insertions(+), 123 deletions(-)
 create mode 100644 src/video_core/renderer_opengl/gl_sampler_cache.cpp
 create mode 100644 src/video_core/renderer_opengl/gl_sampler_cache.h

diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 1a0cc6df15..6950e9d093 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -46,6 +46,8 @@ add_library(video_core STATIC
     renderer_opengl/gl_rasterizer_cache.h
     renderer_opengl/gl_resource_manager.cpp
     renderer_opengl/gl_resource_manager.h
+    renderer_opengl/gl_sampler_cache.cpp
+    renderer_opengl/gl_sampler_cache.h
     renderer_opengl/gl_shader_cache.cpp
     renderer_opengl/gl_shader_cache.h
     renderer_opengl/gl_shader_decompiler.cpp
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 046fc935b9..40b9f48096 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -103,12 +103,6 @@ struct FramebufferCacheKey {
 RasterizerOpenGL::RasterizerOpenGL(Core::System& system, ScreenInfo& info)
     : res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, system{system},
       screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) {
-    // Create sampler objects
-    for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
-        texture_samplers[i].Create();
-        state.texture_units[i].sampler = texture_samplers[i].sampler.handle;
-    }
-
     OpenGLState::ApplyDefaultState();
 
     shader_program_manager = std::make_unique<GLShader::ProgramManager>();
@@ -807,92 +801,6 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
     return true;
 }
 
-void RasterizerOpenGL::SamplerInfo::Create() {
-    sampler.Create();
-    mag_filter = Tegra::Texture::TextureFilter::Linear;
-    min_filter = Tegra::Texture::TextureFilter::Linear;
-    wrap_u = Tegra::Texture::WrapMode::Wrap;
-    wrap_v = Tegra::Texture::WrapMode::Wrap;
-    wrap_p = Tegra::Texture::WrapMode::Wrap;
-    use_depth_compare = false;
-    depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
-
-    // OpenGL's default is GL_LINEAR_MIPMAP_LINEAR
-    glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
-
-    // Other attributes have correct defaults
-}
-
-void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
-    const GLuint sampler_id = sampler.handle;
-    if (mag_filter != config.mag_filter) {
-        mag_filter = config.mag_filter;
-        glSamplerParameteri(
-            sampler_id, GL_TEXTURE_MAG_FILTER,
-            MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None));
-    }
-    if (min_filter != config.min_filter || mipmap_filter != config.mipmap_filter) {
-        min_filter = config.min_filter;
-        mipmap_filter = config.mipmap_filter;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
-                            MaxwellToGL::TextureFilterMode(min_filter, mipmap_filter));
-    }
-
-    if (wrap_u != config.wrap_u) {
-        wrap_u = config.wrap_u;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u));
-    }
-    if (wrap_v != config.wrap_v) {
-        wrap_v = config.wrap_v;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
-    }
-    if (wrap_p != config.wrap_p) {
-        wrap_p = config.wrap_p;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
-    }
-
-    if (const bool enabled = config.depth_compare_enabled == 1; use_depth_compare != enabled) {
-        use_depth_compare = enabled;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
-                            use_depth_compare ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
-    }
-
-    if (depth_compare_func != config.depth_compare_func) {
-        depth_compare_func = config.depth_compare_func;
-        glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
-                            MaxwellToGL::DepthCompareFunc(depth_compare_func));
-    }
-
-    if (const auto new_border_color = config.GetBorderColor(); border_color != new_border_color) {
-        border_color = new_border_color;
-        glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, border_color.data());
-    }
-
-    if (const float anisotropic = config.GetMaxAnisotropy(); max_anisotropic != anisotropic) {
-        max_anisotropic = anisotropic;
-        if (GLAD_GL_ARB_texture_filter_anisotropic) {
-            glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic);
-        } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
-            glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic);
-        }
-    }
-
-    if (const float min = config.GetMinLod(); min_lod != min) {
-        min_lod = min;
-        glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, min_lod);
-    }
-    if (const float max = config.GetMaxLod(); max_lod != max) {
-        max_lod = max;
-        glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, max_lod);
-    }
-
-    if (const float bias = config.GetLodBias(); lod_bias != bias) {
-        lod_bias = bias;
-        glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, lod_bias);
-    }
-}
-
 void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
                                          const Shader& shader, GLuint program_handle,
                                          BaseBindings base_bindings) {
@@ -988,7 +896,7 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
         const auto texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
         const u32 current_bindpoint = base_bindings.sampler + bindpoint;
 
-        texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
+        state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc);
 
         if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) {
             state.texture_units[current_bindpoint].texture =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 4de565321b..faeeb58ef1 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -28,6 +28,7 @@
 #include "video_core/renderer_opengl/gl_primitive_assembler.h"
 #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
 #include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_sampler_cache.h"
 #include "video_core/renderer_opengl/gl_shader_cache.h"
 #include "video_core/renderer_opengl/gl_shader_gen.h"
 #include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -80,34 +81,6 @@ public:
                   "The maximum size of a global memory must be a multiple of the size of float");
 
 private:
-    class SamplerInfo {
-    public:
-        OGLSampler sampler;
-
-        /// Creates the sampler object, initializing its state so that it's in sync with the
-        /// SamplerInfo struct.
-        void Create();
-        /// Syncs the sampler object with the config, updating any necessary state.
-        void SyncWithConfig(const Tegra::Texture::TSCEntry& info);
-
-    private:
-        Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest;
-        Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest;
-        Tegra::Texture::TextureMipmapFilter mipmap_filter =
-            Tegra::Texture::TextureMipmapFilter::None;
-        Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge;
-        Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge;
-        Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge;
-        bool use_depth_compare = false;
-        Tegra::Texture::DepthCompareFunc depth_compare_func =
-            Tegra::Texture::DepthCompareFunc::Always;
-        GLvec4 border_color = {};
-        float min_lod = 0.0f;
-        float max_lod = 16.0f;
-        float lod_bias = 0.0f;
-        float max_anisotropic = 1.0f;
-    };
-
     struct FramebufferConfigState {
         bool using_color_fb{};
         bool using_depth_fb{};
@@ -212,6 +185,7 @@ private:
     RasterizerCacheOpenGL res_cache;
     ShaderCacheOpenGL shader_cache;
     GlobalRegionCacheOpenGL global_cache;
+    SamplerCacheOpenGL sampler_cache;
 
     Core::System& system;
 
@@ -227,8 +201,6 @@ private:
     FramebufferConfigState current_framebuffer_config_state;
     std::pair<bool, bool> current_depth_stencil_usage{};
 
-    std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
-
     static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
     OGLBufferCache buffer_cache;
     PrimitiveAssembler primitive_assembler{buffer_cache};
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.cpp b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
new file mode 100644
index 0000000000..3ded5ecea2
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_sampler_cache.h"
+#include "video_core/renderer_opengl/maxwell_to_gl.h"
+
+namespace OpenGL {
+
+SamplerCacheOpenGL::SamplerCacheOpenGL() = default;
+
+SamplerCacheOpenGL::~SamplerCacheOpenGL() = default;
+
+OGLSampler SamplerCacheOpenGL::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
+    OGLSampler sampler;
+    sampler.Create();
+
+    const GLuint sampler_id{sampler.handle};
+    glSamplerParameteri(
+        sampler_id, GL_TEXTURE_MAG_FILTER,
+        MaxwellToGL::TextureFilterMode(tsc.mag_filter, Tegra::Texture::TextureMipmapFilter::None));
+    glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
+                        MaxwellToGL::TextureFilterMode(tsc.min_filter, tsc.mipmap_filter));
+    glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(tsc.wrap_u));
+    glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(tsc.wrap_v));
+    glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(tsc.wrap_p));
+    glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
+                        tsc.depth_compare_enabled == 1 ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
+    glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
+                        MaxwellToGL::DepthCompareFunc(tsc.depth_compare_func));
+    glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, tsc.GetBorderColor().data());
+    glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, tsc.GetMinLod());
+    glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, tsc.GetMaxLod());
+    glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, tsc.GetLodBias());
+    if (GLAD_GL_ARB_texture_filter_anisotropic) {
+        glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, tsc.GetMaxAnisotropy());
+    } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
+        glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, tsc.GetMaxAnisotropy());
+    } else if (tsc.GetMaxAnisotropy() != 1) {
+        LOG_WARNING(Render_OpenGL, "Anisotropy not supported by host GPU driver");
+    }
+
+    return sampler;
+}
+
+GLuint SamplerCacheOpenGL::ToSamplerType(const OGLSampler& sampler) const {
+    return sampler.handle;
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.h b/src/video_core/renderer_opengl/gl_sampler_cache.h
new file mode 100644
index 0000000000..defbc2d819
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.h
@@ -0,0 +1,25 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <glad/glad.h>
+
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/sampler_cache.h"
+
+namespace OpenGL {
+
+class SamplerCacheOpenGL final : public VideoCommon::SamplerCache<GLuint, OGLSampler> {
+public:
+    explicit SamplerCacheOpenGL();
+    ~SamplerCacheOpenGL();
+
+protected:
+    OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
+
+    GLuint ToSamplerType(const OGLSampler& sampler) const;
+};
+
+} // namespace OpenGL