video_core: Manage samplers in the rasterizer cache

This commit is contained in:
GPUCode
2023-02-13 22:21:48 +02:00
parent c57a912e30
commit e4502a1a6c
14 changed files with 468 additions and 235 deletions

View File

@@ -34,6 +34,8 @@ add_library(video_core STATIC
rasterizer_cache/rasterizer_cache.cpp rasterizer_cache/rasterizer_cache.cpp
rasterizer_cache/rasterizer_cache.h rasterizer_cache/rasterizer_cache.h
rasterizer_cache/rasterizer_cache_base.h rasterizer_cache/rasterizer_cache_base.h
rasterizer_cache/sampler_params.h
rasterizer_cache/slot_vector.h
rasterizer_cache/surface_base.h rasterizer_cache/surface_base.h
rasterizer_cache/utils.cpp rasterizer_cache/utils.cpp
rasterizer_cache/utils.h rasterizer_cache/utils.h

View File

@@ -27,7 +27,18 @@ inline auto RangeFromInterval(auto& map, const auto& interval) {
template <class T> template <class T>
RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_, TextureRuntime& runtime_) RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_, TextureRuntime& runtime_)
: memory{memory_}, runtime{runtime_}, resolution_scale_factor{ : 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 <class T> template <class T>
RasterizerCache<T>::~RasterizerCache() { RasterizerCache<T>::~RasterizerCache() {
@@ -205,6 +216,33 @@ bool RasterizerCache<T>::AccelerateFill(const GPU::Regs::MemoryFillConfig& confi
return true; return true;
} }
template <class T>
auto RasterizerCache<T>::GetSampler(SamplerId sampler_id) -> Sampler& {
return slot_samplers[sampler_id];
}
template <class T>
auto RasterizerCache<T>::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 <class T> template <class T>
template <typename Func> template <typename Func>
void RasterizerCache<T>::ForEachSurfaceInRegion(PAddr addr, size_t size, Func&& func) { void RasterizerCache<T>::ForEachSurfaceInRegion(PAddr addr, size_t size, Func&& func) {

View File

@@ -9,6 +9,7 @@
#include <unordered_map> #include <unordered_map>
#include <boost/icl/interval_map.hpp> #include <boost/icl/interval_map.hpp>
#include <boost/range/iterator_range.hpp> #include <boost/range/iterator_range.hpp>
#include "video_core/rasterizer_cache/sampler_params.h"
#include "video_core/rasterizer_cache/surface_base.h" #include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/rasterizer_cache/surface_params.h" #include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/rasterizer_cache/utils.h" #include "video_core/rasterizer_cache/utils.h"
@@ -44,6 +45,7 @@ class RasterizerCache : NonCopyable {
static constexpr u64 CITRA_PAGEBITS = 18; static constexpr u64 CITRA_PAGEBITS = 18;
using TextureRuntime = typename T::RuntimeType; using TextureRuntime = typename T::RuntimeType;
using Sampler = typename T::Sampler;
using Surface = std::shared_ptr<typename T::SurfaceType>; using Surface = std::shared_ptr<typename T::SurfaceType>;
using Watcher = SurfaceWatcher<typename T::SurfaceType>; using Watcher = SurfaceWatcher<typename T::SurfaceType>;
@@ -69,6 +71,10 @@ public:
/// Perform hardware accelerated memory fill according to the provided configuration /// Perform hardware accelerated memory fill according to the provided configuration
bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config); 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 /// Copy one surface's region to another
void CopySurface(const Surface& src_surface, const Surface& dst_surface, void CopySurface(const Surface& src_surface, const Surface& dst_surface,
SurfaceInterval copy_interval); SurfaceInterval copy_interval);
@@ -188,6 +194,9 @@ private:
// This fits better for the purpose of this cache as textures are normaly // This fits better for the purpose of this cache as textures are normaly
// large in size. // large in size.
std::unordered_map<u64, std::vector<Surface>, Common::IdentityHash<u64>> page_table; std::unordered_map<u64, std::vector<Surface>, Common::IdentityHash<u64>> page_table;
std::unordered_map<SamplerParams, SamplerId> samplers;
SlotVector<Sampler> slot_samplers;
}; };
} // namespace VideoCore } // namespace VideoCore

View File

@@ -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<VideoCore::SamplerParams> {
std::size_t operator()(const VideoCore::SamplerParams& params) const noexcept {
return params.Hash();
}
};
} // namespace std

View File

@@ -0,0 +1,224 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <bit>
#include <numeric>
#include <type_traits>
#include <utility>
#include <vector>
#include "common/assert.h"
#include "common/common_types.h"
namespace VideoCore {
struct SlotId {
static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::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 T>
class SlotVector {
public:
class Iterator {
friend SlotVector<T>;
public:
constexpr Iterator() = default;
Iterator& operator++() noexcept {
const u64* const bitset = slot_vector->stored_bitset.data();
const u32 size = static_cast<u32>(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<SlotId, T*> operator*() const noexcept {
return {id, std::addressof((*slot_vector)[id])};
}
T* operator->() const noexcept {
return std::addressof((*slot_vector)[id]);
}
private:
Iterator(SlotVector<T>* 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<T>* 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 <typename... Args>
[[nodiscard]] SlotId insert(Args&&... args) noexcept {
const u32 index = FreeValueIndex();
new (&values[index].object) T(std::forward<Args>(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<u32>(std::distance(it, stored_bitset.begin()));
const SlotId first_id{word_index * 64 + static_cast<u32>(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<u32>(values_capacity));
delete[] values;
values = new_values;
values_capacity = new_capacity;
}
Entry* values = nullptr;
size_t values_capacity = 0;
std::vector<u64> stored_bitset;
std::vector<u32> free_list;
};
} // namespace VideoCore
template <>
struct std::hash<VideoCore::SlotId> {
size_t operator()(const VideoCore::SlotId& id) const noexcept {
return std::hash<u32>{}(id.index);
}
};

View File

@@ -9,12 +9,17 @@
#include "common/math_util.h" #include "common/math_util.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/rasterizer_cache/slot_vector.h"
namespace VideoCore { namespace VideoCore {
class SurfaceParams;
using Rect2D = Common::Rectangle<u32>; using Rect2D = Common::Rectangle<u32>;
using SamplerId = SlotId;
/// Fake sampler ID for null samplers
constexpr SamplerId NULL_SAMPLER_ID{0};
struct Offset { struct Offset {
constexpr auto operator<=>(const Offset&) const noexcept = default; 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); [[nodiscard]] ClearValue MakeClearValue(SurfaceType type, PixelFormat format, const u8* fill_data);
u32 MipLevels(u32 width, u32 height, u32 max_level); u32 MipLevels(u32 width, u32 height, u32 max_level);

View File

@@ -43,16 +43,6 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory_, Frontend::EmuW
u8 framebuffer_data[4] = {0, 0, 0, 1}; 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); 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 // Generate VAO
sw_vao.Create(); sw_vao.Create();
hw_vao.Create(); hw_vao.Create();
@@ -491,6 +481,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
const auto& texture = pica_textures[texture_index]; const auto& texture = pica_textures[texture_index];
if (texture.enabled) { if (texture.enabled) {
Sampler& sampler = res_cache.GetSampler(texture.config);
if (texture_index == 0) { if (texture_index == 0) {
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType; using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
switch (texture.config.type.Value()) { switch (texture.config.type.Value()) {
@@ -530,7 +521,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
state.texture_cube_unit.texture_cube = state.texture_cube_unit.texture_cube =
res_cache.GetTextureCube(config)->texture.handle; 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; state.texture_units[texture_index].texture_2d = 0;
continue; // Texture unit 0 setup finished. Continue to next unit 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; 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); auto surface = res_cache.GetTextureSurface(texture);
if (surface != nullptr) { if (surface != nullptr) {
CheckBarrier(state.texture_units[texture_index].texture_2d = CheckBarrier(state.texture_units[texture_index].texture_2d =
@@ -822,75 +814,6 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con
return true; 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<float>(lod_max));
glSamplerParameterf(sampler.handle, GL_TEXTURE_MIN_LOD, static_cast<float>(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<float>(lod_min));
}
if (lod_max != config.lod.max_level) {
lod_max = config.lod.max_level;
glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, static_cast<float>(lod_max));
}
}
void RasterizerOpenGL::SetShader() { void RasterizerOpenGL::SetShader() {
shader_program_manager.UseFragmentShader(regs); shader_program_manager.UseFragmentShader(regs);
} }

View File

@@ -47,31 +47,6 @@ public:
void SyncFixedState() override; void SyncFixedState() override;
private: 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; void NotifyFixedFunctionPicaRegisterChanged(u32 id) override;
/// Syncs the clip enabled status to match the PICA register /// Syncs the clip enabled status to match the PICA register
@@ -145,7 +120,6 @@ private:
std::array<bool, 16> hw_vao_enabled_attributes{}; std::array<bool, 16> hw_vao_enabled_attributes{};
OGLTexture default_texture; OGLTexture default_texture;
std::array<SamplerInfo, 3> texture_samplers;
StreamBuffer vertex_buffer; StreamBuffer vertex_buffer;
StreamBuffer uniform_buffer; StreamBuffer uniform_buffer;
StreamBuffer index_buffer; StreamBuffer index_buffer;
@@ -156,8 +130,6 @@ private:
std::size_t uniform_size_aligned_vs; std::size_t uniform_size_aligned_vs;
std::size_t uniform_size_aligned_fs; std::size_t uniform_size_aligned_fs;
SamplerInfo texture_cube_sampler;
OGLTexture texture_buffer_lut_lf; OGLTexture texture_buffer_lut_lf;
OGLTexture texture_buffer_lut_rg; OGLTexture texture_buffer_lut_rg;
OGLTexture texture_buffer_lut_rgba; OGLTexture texture_buffer_lut_rgba;

View File

@@ -10,6 +10,7 @@
#include "video_core/renderer_opengl/gl_format_reinterpreter.h" #include "video_core/renderer_opengl/gl_format_reinterpreter.h"
#include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_texture_runtime.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" #include "video_core/video_core.h"
namespace OpenGL { 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 } // namespace OpenGL

View File

@@ -135,9 +135,29 @@ public:
OGLTexture texture{}; 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 { struct Traits {
using RuntimeType = TextureRuntime; using RuntimeType = TextureRuntime;
using SurfaceType = Surface; using SurfaceType = Surface;
using Sampler = Sampler;
}; };
using RasterizerCache = VideoCore::RasterizerCache<Traits>; using RasterizerCache = VideoCore::RasterizerCache<Traits>;

View File

@@ -92,16 +92,6 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory_, Frontend::EmuW
MakeSoftwareVertexLayout(); MakeSoftwareVertexLayout();
pipeline_info.vertex_layout = software_layout; 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(); const vk::Device device = instance.GetDevice();
texture_lf_view = device.createBufferView({ texture_lf_view = device.createBufferView({
.buffer = texture_lf_buffer.Handle(), .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(3, texture_rg_view);
pipeline_cache.BindTexelBuffer(4, texture_rgba_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++) { for (u32 i = 0; i < 4; i++) {
pipeline_cache.BindTexture(i, null_surface.ImageView()); 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++) { for (u32 i = 0; i < 7; i++) {
@@ -148,10 +139,6 @@ RasterizerVulkan::~RasterizerVulkan() {
scheduler.Finish(); scheduler.Finish();
const vk::Device device = instance.GetDevice(); const vk::Device device = instance.GetDevice();
for (auto& [key, sampler] : samplers) {
device.destroySampler(sampler);
}
device.destroySampler(default_sampler); device.destroySampler(default_sampler);
device.destroyBufferView(texture_lf_view); device.destroyBufferView(texture_lf_view);
device.destroyBufferView(texture_rg_view); device.destroyBufferView(texture_rg_view);
@@ -625,6 +612,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) {
const auto& texture = pica_textures[texture_index]; const auto& texture = pica_textures[texture_index];
if (texture.enabled) { if (texture.enabled) {
Sampler& sampler = res_cache.GetSampler(texture.config);
if (texture_index == 0) { if (texture_index == 0) {
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType; using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
switch (texture.config.type.Value()) { switch (texture.config.type.Value()) {
@@ -676,7 +664,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) {
pipeline_cache.BindTexture(3, null_surface.ImageView()); 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 continue; // Texture unit 0 setup finished. Continue to next unit
} }
default: default:
@@ -684,8 +672,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) {
} }
} }
// Update sampler key pipeline_cache.BindSampler(texture_index, sampler.Handle());
BindSampler(texture_index, texture_samplers[texture_index], texture.config);
auto surface = res_cache.GetTextureSurface(texture); auto surface = res_cache.GetTextureSurface(texture);
if (surface) { if (surface) {
@@ -717,8 +704,9 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) {
pipeline_cache.BindTexture(texture_index, null_surface.ImageView()); pipeline_cache.BindTexture(texture_index, null_surface.ImageView());
} }
} else { } else {
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
pipeline_cache.BindTexture(texture_index, null_surface.ImageView()); 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<float>(config.lod.min_level),
.lod_max = skip_mipmap ? 0.f : static_cast<float>(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() { void RasterizerVulkan::SyncClipEnabled() {
bool clip_enabled = regs.rasterizer.clip_enable != 0; bool clip_enabled = regs.rasterizer.clip_enable != 0;
if (clip_enabled != uniform_block_data.data.enable_clip1) { if (clip_enabled != uniform_block_data.data.enable_clip1) {

View File

@@ -24,36 +24,6 @@ class Scheduler;
class RenderpassCache; class RenderpassCache;
class DescriptorManager; 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<Vulkan::SamplerInfo> {
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 { class RasterizerVulkan : public VideoCore::RasterizerAccelerated {
friend class RendererVulkan; friend class RendererVulkan;
@@ -152,12 +122,6 @@ private:
/// Creates the vertex layout struct used for software shader pipelines /// Creates the vertex layout struct used for software shader pipelines
void MakeSoftwareVertexLayout(); 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: private:
const Instance& instance; const Instance& instance;
Scheduler& scheduler; Scheduler& scheduler;
@@ -174,10 +138,6 @@ private:
vk::Sampler default_sampler; vk::Sampler default_sampler;
Surface null_surface; Surface null_surface;
Surface null_storage_surface; Surface null_storage_surface;
std::array<SamplerInfo, 3> texture_samplers;
SamplerInfo texture_cube_sampler;
std::unordered_map<SamplerInfo, vk::Sampler> samplers;
PipelineInfo pipeline_info; PipelineInfo pipeline_info;
StreamBuffer stream_buffer; ///< Vertex+Index+Uniform buffer StreamBuffer stream_buffer; ///< Vertex+Index+Uniform buffer

View File

@@ -5,6 +5,7 @@
#include "common/microprofile.h" #include "common/microprofile.h"
#include "video_core/rasterizer_cache/texture_codec.h" #include "video_core/rasterizer_cache/texture_codec.h"
#include "video_core/rasterizer_cache/utils.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_instance.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -52,9 +53,9 @@ struct RecordParams {
return value; return value;
} }
[[nodiscard]] vk::ClearColorValue MakeClearColorValue(VideoCore::ClearValue clear) { [[nodiscard]] vk::ClearColorValue MakeClearColorValue(Common::Vec4f color) {
return vk::ClearColorValue{ 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<bool>(params.aspect & vk::ImageAspectFlagBits::eColor); static_cast<bool>(params.aspect & vk::ImageAspectFlagBits::eColor);
if (is_color) { if (is_color) {
cmdbuf.clearColorImage(params.src_image, vk::ImageLayout::eTransferDstOptimal, cmdbuf.clearColorImage(params.src_image, vk::ImageLayout::eTransferDstOptimal,
MakeClearColorValue(value), range); MakeClearColorValue(value.color), range);
} else { } else {
cmdbuf.clearDepthStencilImage(params.src_image, cmdbuf.clearDepthStencilImage(params.src_image,
vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferDstOptimal,
@@ -1229,4 +1230,56 @@ void Surface::DepthStencilDownload(const VideoCore::BufferTextureCopy& download,
r32_surface.Download(r32_download, staging); 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<float>(params.lod_min);
const float lod_max = static_cast<float>(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 } // namespace Vulkan

View File

@@ -86,6 +86,7 @@ class Surface;
*/ */
class TextureRuntime { class TextureRuntime {
friend class Surface; friend class Surface;
friend class Sampler;
public: public:
TextureRuntime(const Instance& instance, Scheduler& scheduler, TextureRuntime(const Instance& instance, Scheduler& scheduler,
@@ -235,9 +236,40 @@ private:
bool is_storage{}; 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 { struct Traits {
using RuntimeType = TextureRuntime; using RuntimeType = TextureRuntime;
using SurfaceType = Surface; using SurfaceType = Surface;
using Sampler = Sampler;
}; };
using RasterizerCache = VideoCore::RasterizerCache<Traits>; using RasterizerCache = VideoCore::RasterizerCache<Traits>;