From 45ffc567333475b639b10490b0793e156d59702d Mon Sep 17 00:00:00 2001 From: GPUCode Date: Mon, 13 Feb 2023 22:21:48 +0200 Subject: [PATCH] video_core: Manage samplers in the rasterizer cache --- src/video_core/CMakeLists.txt | 2 + .../rasterizer_cache/rasterizer_cache.h | 40 +++- .../rasterizer_cache/rasterizer_cache_base.h | 9 + .../rasterizer_cache/sampler_params.h | 40 ++++ src/video_core/rasterizer_cache/slot_vector.h | 224 ++++++++++++++++++ src/video_core/rasterizer_cache/utils.h | 9 +- .../renderer_opengl/gl_rasterizer.cpp | 85 +------ .../renderer_opengl/gl_rasterizer.h | 28 --- .../renderer_opengl/gl_texture_runtime.cpp | 27 +++ .../renderer_opengl/gl_texture_runtime.h | 20 ++ .../renderer_vulkan/vk_rasterizer.cpp | 88 +------ .../renderer_vulkan/vk_rasterizer.h | 40 ---- .../renderer_vulkan/vk_texture_runtime.cpp | 59 ++++- .../renderer_vulkan/vk_texture_runtime.h | 32 +++ 14 files changed, 468 insertions(+), 235 deletions(-) create mode 100644 src/video_core/rasterizer_cache/sampler_params.h create mode 100644 src/video_core/rasterizer_cache/slot_vector.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 816d4440f..483327b1f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -34,6 +34,8 @@ add_library(video_core STATIC rasterizer_cache/rasterizer_cache.cpp rasterizer_cache/rasterizer_cache.h rasterizer_cache/rasterizer_cache_base.h + rasterizer_cache/sampler_params.h + rasterizer_cache/slot_vector.h rasterizer_cache/surface_base.h rasterizer_cache/utils.cpp rasterizer_cache/utils.h diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 6766dcf42..56481b1ce 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -27,7 +27,18 @@ inline auto RangeFromInterval(auto& map, const auto& interval) { template RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, TextureRuntime& runtime_) : memory{memory_}, runtime{runtime_}, resolution_scale_factor{ - VideoCore::GetResolutionScaleFactor()} {} + VideoCore::GetResolutionScaleFactor()} { + using TextureConfig = Pica::TexturingRegs::TextureConfig; + + // Create null handles for all cached resources + void(slot_samplers.insert(runtime, SamplerParams{ + .mag_filter = TextureConfig::TextureFilter::Linear, + .min_filter = TextureConfig::TextureFilter::Linear, + .mip_filter = TextureConfig::TextureFilter::Linear, + .wrap_s = TextureConfig::WrapMode::ClampToBorder, + .wrap_t = TextureConfig::WrapMode::ClampToBorder, + })); +} template RasterizerCache::~RasterizerCache() { @@ -205,6 +216,33 @@ bool RasterizerCache::AccelerateFill(const GPU::Regs::MemoryFillConfig& confi return true; } +template +auto RasterizerCache::GetSampler(SamplerId sampler_id) -> Sampler& { + return slot_samplers[sampler_id]; +} + +template +auto RasterizerCache::GetSampler(const Pica::TexturingRegs::TextureConfig& config) -> Sampler& { + const SamplerParams params = { + .mag_filter = config.mag_filter, + .min_filter = config.min_filter, + .mip_filter = config.mip_filter, + .wrap_s = config.wrap_s, + .wrap_t = config.wrap_t, + .border_color = config.border_color.raw, + .lod_min = config.lod.min_level, + .lod_max = config.lod.max_level, + .lod_bias = config.lod.bias, + }; + + auto [it, is_new] = samplers.try_emplace(params); + if (is_new) { + it->second = slot_samplers.insert(runtime, params); + } + + return slot_samplers[it->second]; +} + template template void RasterizerCache::ForEachSurfaceInRegion(PAddr addr, size_t size, Func&& func) { diff --git a/src/video_core/rasterizer_cache/rasterizer_cache_base.h b/src/video_core/rasterizer_cache/rasterizer_cache_base.h index 9a719d466..e80995974 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache_base.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache_base.h @@ -9,6 +9,7 @@ #include #include #include +#include "video_core/rasterizer_cache/sampler_params.h" #include "video_core/rasterizer_cache/surface_base.h" #include "video_core/rasterizer_cache/surface_params.h" #include "video_core/rasterizer_cache/utils.h" @@ -44,6 +45,7 @@ class RasterizerCache : NonCopyable { static constexpr u64 CITRA_PAGEBITS = 18; using TextureRuntime = typename T::RuntimeType; + using Sampler = typename T::Sampler; using Surface = std::shared_ptr; using Watcher = SurfaceWatcher; @@ -69,6 +71,10 @@ public: /// Perform hardware accelerated memory fill according to the provided configuration bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config); + /// Returns a reference to the sampler object matching the provided configuration + Sampler& GetSampler(const Pica::TexturingRegs::TextureConfig& config); + Sampler& GetSampler(SamplerId sampler_id); + /// Copy one surface's region to another void CopySurface(const Surface& src_surface, const Surface& dst_surface, SurfaceInterval copy_interval); @@ -188,6 +194,9 @@ private: // This fits better for the purpose of this cache as textures are normaly // large in size. std::unordered_map, Common::IdentityHash> page_table; + std::unordered_map samplers; + + SlotVector slot_samplers; }; } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/sampler_params.h b/src/video_core/rasterizer_cache/sampler_params.h new file mode 100644 index 000000000..ec63d92f1 --- /dev/null +++ b/src/video_core/rasterizer_cache/sampler_params.h @@ -0,0 +1,40 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/hash.h" +#include "video_core/regs_texturing.h" + +namespace VideoCore { + +struct SamplerParams { + using TextureConfig = Pica::TexturingRegs::TextureConfig; + TextureConfig::TextureFilter mag_filter; + TextureConfig::TextureFilter min_filter; + TextureConfig::TextureFilter mip_filter; + TextureConfig::WrapMode wrap_s; + TextureConfig::WrapMode wrap_t; + u32 border_color = 0; + u32 lod_min = 0; + u32 lod_max = 0; + s32 lod_bias = 0; + + auto operator<=>(const SamplerParams&) const noexcept = default; + + const u64 Hash() const { + return Common::ComputeHash64(this, sizeof(SamplerParams)); + } +}; + +} // namespace VideoCore + +namespace std { +template <> +struct hash { + std::size_t operator()(const VideoCore::SamplerParams& params) const noexcept { + return params.Hash(); + } +}; +} // namespace std diff --git a/src/video_core/rasterizer_cache/slot_vector.h b/src/video_core/rasterizer_cache/slot_vector.h new file mode 100644 index 000000000..3ac3ffe98 --- /dev/null +++ b/src/video_core/rasterizer_cache/slot_vector.h @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" + +namespace VideoCore { + +struct SlotId { + static constexpr u32 INVALID_INDEX = std::numeric_limits::max(); + + constexpr auto operator<=>(const SlotId&) const noexcept = default; + + constexpr explicit operator bool() const noexcept { + return index != INVALID_INDEX; + } + + u32 index = INVALID_INDEX; +}; + +template +class SlotVector { +public: + class Iterator { + friend SlotVector; + + public: + constexpr Iterator() = default; + + Iterator& operator++() noexcept { + const u64* const bitset = slot_vector->stored_bitset.data(); + const u32 size = static_cast(slot_vector->stored_bitset.size()) * 64; + if (id.index < size) { + do { + ++id.index; + } while (id.index < size && !IsValid(bitset)); + if (id.index == size) { + id.index = SlotId::INVALID_INDEX; + } + } + return *this; + } + + Iterator operator++(int) noexcept { + const Iterator copy{*this}; + ++*this; + return copy; + } + + bool operator==(const Iterator& other) const noexcept { + return id.index == other.id.index; + } + + bool operator!=(const Iterator& other) const noexcept { + return id.index != other.id.index; + } + + std::pair operator*() const noexcept { + return {id, std::addressof((*slot_vector)[id])}; + } + + T* operator->() const noexcept { + return std::addressof((*slot_vector)[id]); + } + + private: + Iterator(SlotVector* slot_vector_, SlotId id_) noexcept + : slot_vector{slot_vector_}, id{id_} {} + + bool IsValid(const u64* bitset) const noexcept { + return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0; + } + + SlotVector* slot_vector; + SlotId id; + }; + + ~SlotVector() noexcept { + size_t index = 0; + for (u64 bits : stored_bitset) { + for (size_t bit = 0; bits; ++bit, bits >>= 1) { + if ((bits & 1) != 0) { + values[index + bit].object.~T(); + } + } + index += 64; + } + delete[] values; + } + + [[nodiscard]] T& operator[](SlotId id) noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + [[nodiscard]] const T& operator[](SlotId id) const noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + template + [[nodiscard]] SlotId insert(Args&&... args) noexcept { + const u32 index = FreeValueIndex(); + new (&values[index].object) T(std::forward(args)...); + SetStorageBit(index); + + return SlotId{index}; + } + + void erase(SlotId id) noexcept { + values[id.index].object.~T(); + free_list.push_back(id.index); + ResetStorageBit(id.index); + } + + [[nodiscard]] Iterator begin() noexcept { + const auto it = std::find_if(stored_bitset.begin(), stored_bitset.end(), + [](u64 value) { return value != 0; }); + if (it == stored_bitset.end()) { + return end(); + } + const u32 word_index = static_cast(std::distance(it, stored_bitset.begin())); + const SlotId first_id{word_index * 64 + static_cast(std::countr_zero(*it))}; + return Iterator(this, first_id); + } + + [[nodiscard]] Iterator end() noexcept { + return Iterator(this, SlotId{SlotId::INVALID_INDEX}); + } + +private: + struct NonTrivialDummy { + NonTrivialDummy() noexcept {} + }; + + union Entry { + Entry() noexcept : dummy{} {} + ~Entry() noexcept {} + + NonTrivialDummy dummy; + T object; + }; + + void SetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] |= u64(1) << (index % 64); + } + + void ResetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] &= ~(u64(1) << (index % 64)); + } + + bool ReadStorageBit(u32 index) noexcept { + return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0; + } + + void ValidateIndex(SlotId id) const noexcept { + DEBUG_ASSERT(id); + DEBUG_ASSERT(id.index / 64 < stored_bitset.size()); + DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0); + } + + [[nodiscard]] u32 FreeValueIndex() noexcept { + if (free_list.empty()) { + Reserve(values_capacity ? (values_capacity << 1) : 1); + } + + const u32 free_index = free_list.back(); + free_list.pop_back(); + return free_index; + } + + void Reserve(size_t new_capacity) noexcept { + Entry* const new_values = new Entry[new_capacity]; + size_t index = 0; + for (u64 bits : stored_bitset) { + T* old{}; + for (size_t bit = 0; bits; ++bit, bits >>= 1) { + const size_t i = index + bit; + if ((bits & 1) == 0) { + continue; + } + T& old_value = values[i].object; + old = &old_value; + new (&new_values[i].object) T(std::move(old_value)); + old_value.~T(); + } + index += 64; + } + + stored_bitset.resize((new_capacity + 63) / 64); + + const size_t old_free_size = free_list.size(); + free_list.resize(old_free_size + (new_capacity - values_capacity)); + std::iota(free_list.begin() + old_free_size, free_list.end(), + static_cast(values_capacity)); + + delete[] values; + values = new_values; + values_capacity = new_capacity; + } + + Entry* values = nullptr; + size_t values_capacity = 0; + + std::vector stored_bitset; + std::vector free_list; +}; + +} // namespace VideoCore + +template <> +struct std::hash { + size_t operator()(const VideoCore::SlotId& id) const noexcept { + return std::hash{}(id.index); + } +}; diff --git a/src/video_core/rasterizer_cache/utils.h b/src/video_core/rasterizer_cache/utils.h index 13a9c8a8c..ca63b81be 100644 --- a/src/video_core/rasterizer_cache/utils.h +++ b/src/video_core/rasterizer_cache/utils.h @@ -9,12 +9,17 @@ #include "common/math_util.h" #include "common/vector_math.h" #include "video_core/rasterizer_cache/pixel_format.h" +#include "video_core/rasterizer_cache/slot_vector.h" namespace VideoCore { -class SurfaceParams; using Rect2D = Common::Rectangle; +using SamplerId = SlotId; + +/// Fake sampler ID for null samplers +constexpr SamplerId NULL_SAMPLER_ID{0}; + struct Offset { constexpr auto operator<=>(const Offset&) const noexcept = default; @@ -105,6 +110,8 @@ struct TextureCubeConfig { } }; +class SurfaceParams; + [[nodiscard]] ClearValue MakeClearValue(SurfaceType type, PixelFormat format, const u8* fill_data); u32 MipLevels(u32 width, u32 height, u32 max_level); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 587fd17a3..338a9d45a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -43,16 +43,6 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory_, Frontend::EmuW u8 framebuffer_data[4] = {0, 0, 0, 1}; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data); - // 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; - } - - // Create cubemap texture and sampler objects - texture_cube_sampler.Create(); - state.texture_cube_unit.sampler = texture_cube_sampler.sampler.handle; - // Generate VAO sw_vao.Create(); hw_vao.Create(); @@ -491,6 +481,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { const auto& texture = pica_textures[texture_index]; if (texture.enabled) { + Sampler& sampler = res_cache.GetSampler(texture.config); if (texture_index == 0) { using TextureType = Pica::TexturingRegs::TextureConfig::TextureType; switch (texture.config.type.Value()) { @@ -530,7 +521,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { state.texture_cube_unit.texture_cube = res_cache.GetTextureCube(config)->texture.handle; - texture_cube_sampler.SyncWithConfig(texture.config); + state.texture_cube_unit.sampler = sampler.Handle(); state.texture_units[texture_index].texture_2d = 0; continue; // Texture unit 0 setup finished. Continue to next unit } @@ -541,7 +532,8 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { state.texture_cube_unit.texture_cube = 0; } - texture_samplers[texture_index].SyncWithConfig(texture.config); + state.texture_units[texture_index].sampler = sampler.Handle(); + auto surface = res_cache.GetTextureSurface(texture); if (surface != nullptr) { CheckBarrier(state.texture_units[texture_index].texture_2d = @@ -822,75 +814,6 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con return true; } -void RasterizerOpenGL::SamplerInfo::Create() { - sampler.Create(); - mag_filter = min_filter = mip_filter = TextureConfig::Linear; - wrap_s = wrap_t = TextureConfig::Repeat; - border_color = 0; - lod_min = lod_max = 0; - - // default is 1000 and -1000 - // Other attributes have correct defaults - glSamplerParameterf(sampler.handle, GL_TEXTURE_MAX_LOD, static_cast(lod_max)); - glSamplerParameterf(sampler.handle, GL_TEXTURE_MIN_LOD, static_cast(lod_min)); -} - -void RasterizerOpenGL::SamplerInfo::SyncWithConfig( - const Pica::TexturingRegs::TextureConfig& config) { - - GLuint s = sampler.handle; - - if (mag_filter != config.mag_filter) { - mag_filter = config.mag_filter; - glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureMagFilterMode(mag_filter)); - } - - // TODO(wwylele): remove new_supress_mipmap_for_cube logic once mipmap for cube is implemented - bool new_supress_mipmap_for_cube = - config.type == Pica::TexturingRegs::TextureConfig::TextureCube; - if (min_filter != config.min_filter || mip_filter != config.mip_filter || - supress_mipmap_for_cube != new_supress_mipmap_for_cube) { - min_filter = config.min_filter; - mip_filter = config.mip_filter; - supress_mipmap_for_cube = new_supress_mipmap_for_cube; - if (new_supress_mipmap_for_cube) { - // HACK: use mag filter converter for min filter because they are the same anyway - glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, - PicaToGL::TextureMagFilterMode(min_filter)); - } else { - glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, - PicaToGL::TextureMinFilterMode(min_filter, mip_filter)); - } - } - - if (wrap_s != config.wrap_s) { - wrap_s = config.wrap_s; - glSamplerParameteri(s, GL_TEXTURE_WRAP_S, PicaToGL::WrapMode(wrap_s)); - } - if (wrap_t != config.wrap_t) { - wrap_t = config.wrap_t; - glSamplerParameteri(s, GL_TEXTURE_WRAP_T, PicaToGL::WrapMode(wrap_t)); - } - - if (wrap_s == TextureConfig::ClampToBorder || wrap_t == TextureConfig::ClampToBorder) { - if (border_color != config.border_color.raw) { - border_color = config.border_color.raw; - auto gl_color = PicaToGL::ColorRGBA8(border_color); - glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.AsArray()); - } - } - - if (lod_min != config.lod.min_level) { - lod_min = config.lod.min_level; - glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, static_cast(lod_min)); - } - - if (lod_max != config.lod.max_level) { - lod_max = config.lod.max_level; - glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, static_cast(lod_max)); - } -} - void RasterizerOpenGL::SetShader() { shader_program_manager.UseFragmentShader(regs); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 09f3c47e4..81d1744bc 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -47,31 +47,6 @@ public: void SyncFixedState() override; private: - struct SamplerInfo { - using TextureConfig = Pica::TexturingRegs::TextureConfig; - - 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 TextureConfig& config); - - private: - TextureConfig::TextureFilter mag_filter; - TextureConfig::TextureFilter min_filter; - TextureConfig::TextureFilter mip_filter; - TextureConfig::WrapMode wrap_s; - TextureConfig::WrapMode wrap_t; - u32 border_color; - u32 lod_min; - u32 lod_max; - - // TODO(wwylele): remove this once mipmap for cube is implemented - bool supress_mipmap_for_cube = false; - }; - void NotifyFixedFunctionPicaRegisterChanged(u32 id) override; /// Syncs the clip enabled status to match the PICA register @@ -145,7 +120,6 @@ private: std::array hw_vao_enabled_attributes{}; OGLTexture default_texture; - std::array texture_samplers; StreamBuffer vertex_buffer; StreamBuffer uniform_buffer; StreamBuffer index_buffer; @@ -156,8 +130,6 @@ private: std::size_t uniform_size_aligned_vs; std::size_t uniform_size_aligned_fs; - SamplerInfo texture_cube_sampler; - OGLTexture texture_buffer_lut_lf; OGLTexture texture_buffer_lut_rg; OGLTexture texture_buffer_lut_rgba; diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.cpp b/src/video_core/renderer_opengl/gl_texture_runtime.cpp index 424aeba1a..35e88ad00 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.cpp +++ b/src/video_core/renderer_opengl/gl_texture_runtime.cpp @@ -10,6 +10,7 @@ #include "video_core/renderer_opengl/gl_format_reinterpreter.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_texture_runtime.h" +#include "video_core/renderer_opengl/pica_to_gl.h" #include "video_core/video_core.h" namespace OpenGL { @@ -472,4 +473,30 @@ void Surface::ScaledDownload(const VideoCore::BufferTextureCopy& download, } } +Sampler::Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params) { + const GLenum mag_filter = PicaToGL::TextureMagFilterMode(params.min_filter); + const GLenum min_filter = PicaToGL::TextureMinFilterMode(params.min_filter, params.mip_filter); + const GLenum wrap_s = PicaToGL::WrapMode(params.wrap_s); + const GLenum wrap_t = PicaToGL::WrapMode(params.wrap_t); + const Common::Vec4f gl_color = PicaToGL::ColorRGBA8(params.border_color); + const float lod_min = params.lod_min; + const float lod_max = params.lod_max; + + sampler.Create(); + + const GLuint handle = sampler.handle; + glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag_filter); + glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min_filter); + + glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, wrap_s); + glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, wrap_t); + + glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, gl_color.AsArray()); + + glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, lod_min); + glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, lod_max); +} + +Sampler::~Sampler() = default; + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.h b/src/video_core/renderer_opengl/gl_texture_runtime.h index d18bd7566..a13900fa1 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.h +++ b/src/video_core/renderer_opengl/gl_texture_runtime.h @@ -135,9 +135,29 @@ public: OGLTexture texture{}; }; +class Sampler { +public: + explicit Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params); + ~Sampler(); + + Sampler(const Sampler&) = delete; + Sampler& operator=(const Sampler&) = delete; + + Sampler(Sampler&&) = default; + Sampler& operator=(Sampler&&) = default; + + [[nodiscard]] GLuint Handle() const noexcept { + return sampler.handle; + } + +private: + OGLSampler sampler; +}; + struct Traits { using RuntimeType = TextureRuntime; using SurfaceType = Surface; + using Sampler = Sampler; }; using RasterizerCache = VideoCore::RasterizerCache; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 66b94d530..25d99a7b8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -92,16 +92,6 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory_, Frontend::EmuW MakeSoftwareVertexLayout(); pipeline_info.vertex_layout = software_layout; - const SamplerInfo default_sampler_info = { - .mag_filter = Pica::TexturingRegs::TextureConfig::TextureFilter::Linear, - .min_filter = Pica::TexturingRegs::TextureConfig::TextureFilter::Linear, - .mip_filter = Pica::TexturingRegs::TextureConfig::TextureFilter::Linear, - .wrap_s = Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder, - .wrap_t = Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder, - }; - - default_sampler = CreateSampler(default_sampler_info); - const vk::Device device = instance.GetDevice(); texture_lf_view = device.createBufferView({ .buffer = texture_lf_buffer.Handle(), @@ -130,9 +120,10 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory_, Frontend::EmuW pipeline_cache.BindTexelBuffer(3, texture_rg_view); pipeline_cache.BindTexelBuffer(4, texture_rgba_view); + const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID); for (u32 i = 0; i < 4; i++) { pipeline_cache.BindTexture(i, null_surface.ImageView()); - pipeline_cache.BindSampler(i, default_sampler); + pipeline_cache.BindSampler(i, null_sampler.Handle()); } for (u32 i = 0; i < 7; i++) { @@ -148,10 +139,6 @@ RasterizerVulkan::~RasterizerVulkan() { scheduler.Finish(); const vk::Device device = instance.GetDevice(); - for (auto& [key, sampler] : samplers) { - device.destroySampler(sampler); - } - device.destroySampler(default_sampler); device.destroyBufferView(texture_lf_view); device.destroyBufferView(texture_rg_view); @@ -625,6 +612,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { const auto& texture = pica_textures[texture_index]; if (texture.enabled) { + Sampler& sampler = res_cache.GetSampler(texture.config); if (texture_index == 0) { using TextureType = Pica::TexturingRegs::TextureConfig::TextureType; switch (texture.config.type.Value()) { @@ -676,7 +664,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { pipeline_cache.BindTexture(3, null_surface.ImageView()); } - BindSampler(3, texture_cube_sampler, texture.config); + pipeline_cache.BindSampler(3, sampler.Handle()); continue; // Texture unit 0 setup finished. Continue to next unit } default: @@ -684,8 +672,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { } } - // Update sampler key - BindSampler(texture_index, texture_samplers[texture_index], texture.config); + pipeline_cache.BindSampler(texture_index, sampler.Handle()); auto surface = res_cache.GetTextureSurface(texture); if (surface) { @@ -717,8 +704,9 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { pipeline_cache.BindTexture(texture_index, null_surface.ImageView()); } } else { + const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID); pipeline_cache.BindTexture(texture_index, null_surface.ImageView()); - pipeline_cache.BindSampler(texture_index, default_sampler); + pipeline_cache.BindSampler(texture_index, null_sampler.Handle()); } } } @@ -888,68 +876,6 @@ void RasterizerVulkan::MakeSoftwareVertexLayout() { } } -void RasterizerVulkan::BindSampler(u32 unit, SamplerInfo& info, - const Pica::TexturingRegs::TextureConfig& config) { - // TODO: Cubemaps don't contain any mipmaps for now, so sampling from them returns - // nothing. Always sample from the base level until mipmaps for texture cubes are - // implemented - const bool skip_mipmap = config.type == Pica::TexturingRegs::TextureConfig::TextureCube; - info = SamplerInfo{ - .mag_filter = config.mag_filter, - .min_filter = config.min_filter, - .mip_filter = config.mip_filter, - .wrap_s = config.wrap_s, - .wrap_t = config.wrap_t, - .border_color = config.border_color.raw, - .lod_min = skip_mipmap ? 0.f : static_cast(config.lod.min_level), - .lod_max = skip_mipmap ? 0.f : static_cast(config.lod.max_level), - }; - - auto [it, new_sampler] = samplers.try_emplace(info); - if (new_sampler) { - it->second = CreateSampler(info); - } - pipeline_cache.BindSampler(unit, it->second); -} - -vk::Sampler RasterizerVulkan::CreateSampler(const SamplerInfo& info) { - const bool use_border_color = instance.IsCustomBorderColorSupported() && - (info.wrap_s == SamplerInfo::TextureConfig::ClampToBorder || - info.wrap_t == SamplerInfo::TextureConfig::ClampToBorder); - auto properties = instance.GetPhysicalDevice().getProperties(); - - const auto color = PicaToVK::ColorRGBA8(info.border_color); - const vk::SamplerCustomBorderColorCreateInfoEXT border_color_info = { - .customBorderColor = - vk::ClearColorValue{ - .float32 = std::array{color[0], color[1], color[2], color[3]}, - }, - .format = vk::Format::eUndefined, - }; - - const vk::SamplerCreateInfo sampler_info = { - .pNext = use_border_color ? &border_color_info : nullptr, - .magFilter = PicaToVK::TextureFilterMode(info.mag_filter), - .minFilter = PicaToVK::TextureFilterMode(info.min_filter), - .mipmapMode = PicaToVK::TextureMipFilterMode(info.mip_filter), - .addressModeU = PicaToVK::WrapMode(info.wrap_s), - .addressModeV = PicaToVK::WrapMode(info.wrap_t), - .mipLodBias = 0, - .anisotropyEnable = instance.IsAnisotropicFilteringSupported(), - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = false, - .compareOp = vk::CompareOp::eAlways, - .minLod = info.lod_min, - .maxLod = info.lod_max, - .borderColor = - use_border_color ? vk::BorderColor::eFloatCustomEXT : vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = false, - }; - - vk::Device device = instance.GetDevice(); - return device.createSampler(sampler_info); -} - void RasterizerVulkan::SyncClipEnabled() { bool clip_enabled = regs.rasterizer.clip_enable != 0; if (clip_enabled != uniform_block_data.data.enable_clip1) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 5318296a2..a75355e04 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -24,36 +24,6 @@ class Scheduler; class RenderpassCache; class DescriptorManager; -struct SamplerInfo { - using TextureConfig = Pica::TexturingRegs::TextureConfig; - TextureConfig::TextureFilter mag_filter; - TextureConfig::TextureFilter min_filter; - TextureConfig::TextureFilter mip_filter; - TextureConfig::WrapMode wrap_s; - TextureConfig::WrapMode wrap_t; - u32 border_color = 0; - float lod_min = 0; - float lod_max = 0; - - // TODO(wwylele): remove this once mipmap for cube is implemented - bool supress_mipmap_for_cube = false; - - auto operator<=>(const SamplerInfo&) const noexcept = default; -}; - -} // namespace Vulkan - -namespace std { -template <> -struct hash { - std::size_t operator()(const Vulkan::SamplerInfo& info) const noexcept { - return Common::ComputeHash64(&info, sizeof(Vulkan::SamplerInfo)); - } -}; -} // namespace std - -namespace Vulkan { - class RasterizerVulkan : public VideoCore::RasterizerAccelerated { friend class RendererVulkan; @@ -152,12 +122,6 @@ private: /// Creates the vertex layout struct used for software shader pipelines void MakeSoftwareVertexLayout(); - /// Binds a sampler to the specified texture unit - void BindSampler(u32 unit, SamplerInfo& info, const Pica::TexturingRegs::TextureConfig& config); - - /// Creates a new sampler object - vk::Sampler CreateSampler(const SamplerInfo& info); - private: const Instance& instance; Scheduler& scheduler; @@ -174,10 +138,6 @@ private: vk::Sampler default_sampler; Surface null_surface; Surface null_storage_surface; - - std::array texture_samplers; - SamplerInfo texture_cube_sampler; - std::unordered_map samplers; PipelineInfo pipeline_info; StreamBuffer stream_buffer; ///< Vertex+Index+Uniform buffer diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index f59f314ed..67533ef4a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -5,6 +5,7 @@ #include "common/microprofile.h" #include "video_core/rasterizer_cache/texture_codec.h" #include "video_core/rasterizer_cache/utils.h" +#include "video_core/renderer_vulkan/pica_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -52,9 +53,9 @@ struct RecordParams { return value; } -[[nodiscard]] vk::ClearColorValue MakeClearColorValue(VideoCore::ClearValue clear) { +[[nodiscard]] vk::ClearColorValue MakeClearColorValue(Common::Vec4f color) { return vk::ClearColorValue{ - .float32 = std::array{clear.color[0], clear.color[1], clear.color[2], clear.color[3]}, + .float32 = std::array{color[0], color[1], color[2], color[3]}, }; } @@ -367,7 +368,7 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea static_cast(params.aspect & vk::ImageAspectFlagBits::eColor); if (is_color) { cmdbuf.clearColorImage(params.src_image, vk::ImageLayout::eTransferDstOptimal, - MakeClearColorValue(value), range); + MakeClearColorValue(value.color), range); } else { cmdbuf.clearDepthStencilImage(params.src_image, vk::ImageLayout::eTransferDstOptimal, @@ -1229,4 +1230,56 @@ void Surface::DepthStencilDownload(const VideoCore::BufferTextureCopy& download, r32_surface.Download(r32_download, staging); } +Sampler::Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params) + : device{runtime.GetInstance().GetDevice()} { + using TextureConfig = VideoCore::SamplerParams::TextureConfig; + + const Instance& instance = runtime.GetInstance(); + const vk::PhysicalDeviceProperties properties = instance.GetPhysicalDevice().getProperties(); + const bool use_border_color = + instance.IsCustomBorderColorSupported() && (params.wrap_s == TextureConfig::ClampToBorder || + params.wrap_t == TextureConfig::ClampToBorder); + + const Common::Vec4f color = PicaToVK::ColorRGBA8(params.border_color); + const vk::SamplerCustomBorderColorCreateInfoEXT border_color_info = { + .customBorderColor = MakeClearColorValue(color), + .format = vk::Format::eUndefined, + }; + + const vk::Filter mag_filter = PicaToVK::TextureFilterMode(params.mag_filter); + const vk::Filter min_filter = PicaToVK::TextureFilterMode(params.min_filter); + const vk::SamplerMipmapMode mipmap_mode = PicaToVK::TextureMipFilterMode(params.mip_filter); + const vk::SamplerAddressMode wrap_u = PicaToVK::WrapMode(params.wrap_s); + const vk::SamplerAddressMode wrap_v = PicaToVK::WrapMode(params.wrap_t); + const float lod_min = static_cast(params.lod_min); + const float lod_max = static_cast(params.lod_max); + + const vk::SamplerCreateInfo sampler_info = { + .pNext = use_border_color ? &border_color_info : nullptr, + .magFilter = mag_filter, + .minFilter = min_filter, + .mipmapMode = mipmap_mode, + .addressModeU = wrap_u, + .addressModeV = wrap_v, + .mipLodBias = 0, + .anisotropyEnable = true, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = false, + .compareOp = vk::CompareOp::eAlways, + .minLod = lod_min, + .maxLod = lod_max, + .borderColor = + use_border_color ? vk::BorderColor::eFloatCustomEXT : vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = false, + }; + + sampler = device.createSampler(sampler_info); +} + +Sampler::~Sampler() { + if (sampler) { + device.destroySampler(sampler); + } +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.h b/src/video_core/renderer_vulkan/vk_texture_runtime.h index cc5ecb9c0..e62c0a981 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.h +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.h @@ -86,6 +86,7 @@ class Surface; */ class TextureRuntime { friend class Surface; + friend class Sampler; public: TextureRuntime(const Instance& instance, Scheduler& scheduler, @@ -235,9 +236,40 @@ private: bool is_storage{}; }; +/** + * @brief A sampler is used to configure the sampling parameters of a texture unit + */ +class Sampler { +public: + Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params); + ~Sampler(); + + Sampler(const Sampler&) = delete; + Sampler& operator=(const Sampler&) = delete; + + Sampler(Sampler&& o) noexcept { + std::memcpy(this, &o, sizeof(Sampler)); + o.sampler = VK_NULL_HANDLE; + } + Sampler& operator=(Sampler&& o) noexcept { + std::memcpy(this, &o, sizeof(Sampler)); + o.sampler = VK_NULL_HANDLE; + return *this; + } + + [[nodiscard]] vk::Sampler Handle() const noexcept { + return sampler; + } + +private: + vk::Device device; + vk::Sampler sampler; +}; + struct Traits { using RuntimeType = TextureRuntime; using SurfaceType = Surface; + using Sampler = Sampler; }; using RasterizerCache = VideoCore::RasterizerCache;