mirror of
https://gitlab.com/suyu-emu/suyu.git
synced 2024-03-15 23:15:44 +00:00
texture_cache: Simplify image view queries and blacklisting
This commit is contained in:
parent
48d81506a3
commit
56ccda1d99
|
@ -79,8 +79,7 @@ void ComputePipeline::Configure() {
|
||||||
}
|
}
|
||||||
texture_cache.SynchronizeComputeDescriptors();
|
texture_cache.SynchronizeComputeDescriptors();
|
||||||
|
|
||||||
std::array<ImageViewId, MAX_TEXTURES + MAX_IMAGES> image_view_ids;
|
boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
|
||||||
boost::container::static_vector<u32, MAX_TEXTURES + MAX_IMAGES> image_view_indices;
|
|
||||||
std::array<GLuint, MAX_TEXTURES> samplers;
|
std::array<GLuint, MAX_TEXTURES> samplers;
|
||||||
std::array<GLuint, MAX_TEXTURES> textures;
|
std::array<GLuint, MAX_TEXTURES> textures;
|
||||||
std::array<GLuint, MAX_IMAGES> images;
|
std::array<GLuint, MAX_IMAGES> images;
|
||||||
|
@ -110,33 +109,39 @@ void ComputePipeline::Configure() {
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc) {
|
const auto add_image{[&](const auto& desc, bool blacklist) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices.push_back(handle.first);
|
views.push_back({
|
||||||
|
.index = handle.first,
|
||||||
|
.blacklist = blacklist,
|
||||||
|
.id = {},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
for (const auto& desc : info.texture_buffer_descriptors) {
|
for (const auto& desc : info.texture_buffer_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices.push_back(handle.first);
|
views.push_back({handle.first});
|
||||||
samplers[sampler_binding++] = 0;
|
samplers[sampler_binding++] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::ranges::for_each(info.image_buffer_descriptors, add_image);
|
for (const auto& desc : info.image_buffer_descriptors) {
|
||||||
|
add_image(desc, false);
|
||||||
|
}
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices.push_back(handle.first);
|
views.push_back({handle.first});
|
||||||
|
|
||||||
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
||||||
samplers[sampler_binding++] = sampler->Handle();
|
samplers[sampler_binding++] = sampler->Handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::ranges::for_each(info.image_descriptors, add_image);
|
for (const auto& desc : info.image_descriptors) {
|
||||||
|
add_image(desc, true);
|
||||||
const std::span indices_span(image_view_indices.data(), image_view_indices.size());
|
}
|
||||||
texture_cache.FillComputeImageViews(indices_span, image_view_ids);
|
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
|
||||||
|
|
||||||
if (assembly_program.handle != 0) {
|
if (assembly_program.handle != 0) {
|
||||||
program_manager.BindComputeAssemblyProgram(assembly_program.handle);
|
program_manager.BindComputeAssemblyProgram(assembly_program.handle);
|
||||||
|
@ -152,7 +157,7 @@ void ComputePipeline::Configure() {
|
||||||
if constexpr (is_image) {
|
if constexpr (is_image) {
|
||||||
is_written = desc.is_written;
|
is_written = desc.is_written;
|
||||||
}
|
}
|
||||||
ImageView& image_view{texture_cache.GetImageView(image_view_ids[texbuf_index])};
|
ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)};
|
||||||
buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
|
buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
|
||||||
image_view.BufferSize(), image_view.format,
|
image_view.BufferSize(), image_view.format,
|
||||||
is_written, is_image);
|
is_written, is_image);
|
||||||
|
@ -168,19 +173,20 @@ void ComputePipeline::Configure() {
|
||||||
buffer_cache.runtime.SetImagePointers(textures.data(), images.data());
|
buffer_cache.runtime.SetImagePointers(textures.data(), images.data());
|
||||||
buffer_cache.BindHostComputeBuffers();
|
buffer_cache.BindHostComputeBuffers();
|
||||||
|
|
||||||
const ImageId* views_it{image_view_ids.data() + num_texture_buffers + num_image_buffers};
|
const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers +
|
||||||
|
num_image_buffers};
|
||||||
texture_binding += num_texture_buffers;
|
texture_binding += num_texture_buffers;
|
||||||
image_binding += num_image_buffers;
|
image_binding += num_image_buffers;
|
||||||
|
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView(*(views_it++))};
|
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||||
textures[texture_binding++] = image_view.Handle(desc.type);
|
textures[texture_binding++] = image_view.Handle(desc.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView(*(views_it++))};
|
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||||
if (desc.is_written) {
|
if (desc.is_written) {
|
||||||
texture_cache.MarkModification(image_view.image_id);
|
texture_cache.MarkModification(image_view.image_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
||||||
#include "video_core/shader_notify.h"
|
#include "video_core/shader_notify.h"
|
||||||
#include "video_core/texture_cache/texture_cache_base.h"
|
#include "video_core/texture_cache/texture_cache.h"
|
||||||
|
|
||||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||||
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
|
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
|
||||||
|
@ -280,10 +280,9 @@ GraphicsPipeline::GraphicsPipeline(
|
||||||
|
|
||||||
template <typename Spec>
|
template <typename Spec>
|
||||||
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
std::array<ImageId, MAX_TEXTURES + MAX_IMAGES> image_view_ids;
|
std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
|
||||||
std::array<u32, MAX_TEXTURES + MAX_IMAGES> image_view_indices;
|
|
||||||
std::array<GLuint, MAX_TEXTURES> samplers;
|
std::array<GLuint, MAX_TEXTURES> samplers;
|
||||||
size_t image_view_index{};
|
size_t views_index{};
|
||||||
GLsizei sampler_binding{};
|
GLsizei sampler_binding{};
|
||||||
|
|
||||||
texture_cache.SynchronizeGraphicsDescriptors();
|
texture_cache.SynchronizeGraphicsDescriptors();
|
||||||
|
@ -328,30 +327,34 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc) {
|
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices[image_view_index++] = handle.first;
|
views[views_index++] = {
|
||||||
|
.index = handle.first,
|
||||||
|
.blacklist = blacklist,
|
||||||
|
.id = {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
if constexpr (Spec::has_texture_buffers) {
|
if constexpr (Spec::has_texture_buffers) {
|
||||||
for (const auto& desc : info.texture_buffer_descriptors) {
|
for (const auto& desc : info.texture_buffer_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices[image_view_index++] = handle.first;
|
views[views_index++] = {handle.first};
|
||||||
samplers[sampler_binding++] = 0;
|
samplers[sampler_binding++] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constexpr (Spec::has_image_buffers) {
|
if constexpr (Spec::has_image_buffers) {
|
||||||
for (const auto& desc : info.image_buffer_descriptors) {
|
for (const auto& desc : info.image_buffer_descriptors) {
|
||||||
add_image(desc);
|
add_image(desc, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices[image_view_index++] = handle.first;
|
views[views_index++] = {handle.first};
|
||||||
|
|
||||||
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
||||||
samplers[sampler_binding++] = sampler->Handle();
|
samplers[sampler_binding++] = sampler->Handle();
|
||||||
|
@ -359,7 +362,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
}
|
}
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
add_image(desc);
|
add_image(desc, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -378,13 +381,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
if constexpr (Spec::enabled_stages[4]) {
|
if constexpr (Spec::enabled_stages[4]) {
|
||||||
config_stage(4);
|
config_stage(4);
|
||||||
}
|
}
|
||||||
const std::span indices_span(image_view_indices.data(), image_view_index);
|
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views_index));
|
||||||
texture_cache.FillGraphicsImageViews(indices_span, image_view_ids);
|
|
||||||
|
|
||||||
texture_cache.UpdateRenderTargets(false);
|
texture_cache.UpdateRenderTargets(false);
|
||||||
state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
|
state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
|
||||||
|
|
||||||
ImageId* texture_buffer_index{image_view_ids.data()};
|
VideoCommon::ImageViewInOut* texture_buffer_it{views.data()};
|
||||||
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
|
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
|
||||||
size_t index{};
|
size_t index{};
|
||||||
const auto add_buffer{[&](const auto& desc) {
|
const auto add_buffer{[&](const auto& desc) {
|
||||||
|
@ -394,12 +396,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
if constexpr (is_image) {
|
if constexpr (is_image) {
|
||||||
is_written = desc.is_written;
|
is_written = desc.is_written;
|
||||||
}
|
}
|
||||||
ImageView& image_view{texture_cache.GetImageView(*texture_buffer_index)};
|
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||||
image_view.BufferSize(), image_view.format,
|
image_view.BufferSize(), image_view.format,
|
||||||
is_written, is_image);
|
is_written, is_image);
|
||||||
++index;
|
++index;
|
||||||
++texture_buffer_index;
|
++texture_buffer_it;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
const Shader::Info& info{stage_infos[stage]};
|
const Shader::Info& info{stage_infos[stage]};
|
||||||
|
@ -415,9 +417,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
add_buffer(desc);
|
add_buffer(desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
texture_buffer_index += Shader::NumDescriptors(info.texture_descriptors);
|
texture_buffer_it += Shader::NumDescriptors(info.texture_descriptors);
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
texture_buffer_index += Shader::NumDescriptors(info.image_descriptors);
|
texture_buffer_it += Shader::NumDescriptors(info.image_descriptors);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
if constexpr (Spec::enabled_stages[0]) {
|
if constexpr (Spec::enabled_stages[0]) {
|
||||||
|
@ -446,7 +448,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
} else {
|
} else {
|
||||||
program_manager.BindSourcePrograms(source_programs);
|
program_manager.BindSourcePrograms(source_programs);
|
||||||
}
|
}
|
||||||
const ImageId* views_it{image_view_ids.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
GLsizei texture_binding = 0;
|
GLsizei texture_binding = 0;
|
||||||
GLsizei image_binding = 0;
|
GLsizei image_binding = 0;
|
||||||
std::array<GLuint, MAX_TEXTURES> textures;
|
std::array<GLuint, MAX_TEXTURES> textures;
|
||||||
|
@ -464,13 +466,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
const auto& info{stage_infos[stage]};
|
const auto& info{stage_infos[stage]};
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView(*(views_it++))};
|
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||||
textures[texture_binding++] = image_view.Handle(desc.type);
|
textures[texture_binding++] = image_view.Handle(desc.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView(*(views_it++))};
|
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||||
if (desc.is_written) {
|
if (desc.is_written) {
|
||||||
texture_cache.MarkModification(image_view.image_id);
|
texture_cache.MarkModification(image_view.image_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -472,11 +472,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager&
|
||||||
set_view(Shader::TextureType::ColorArray1D, null_image_1d_array.handle);
|
set_view(Shader::TextureType::ColorArray1D, null_image_1d_array.handle);
|
||||||
set_view(Shader::TextureType::ColorArray2D, null_image_view_2d_array.handle);
|
set_view(Shader::TextureType::ColorArray2D, null_image_view_2d_array.handle);
|
||||||
set_view(Shader::TextureType::ColorArrayCube, null_image_cube_array.handle);
|
set_view(Shader::TextureType::ColorArrayCube, null_image_cube_array.handle);
|
||||||
}
|
|
||||||
|
|
||||||
TextureCacheRuntime::~TextureCacheRuntime() = default;
|
|
||||||
|
|
||||||
void TextureCacheRuntime::Init() {
|
|
||||||
resolution = Settings::values.resolution_info;
|
resolution = Settings::values.resolution_info;
|
||||||
is_rescaling_on = resolution.up_scale != 1 || resolution.down_shift != 0;
|
is_rescaling_on = resolution.up_scale != 1 || resolution.down_shift != 0;
|
||||||
if (is_rescaling_on) {
|
if (is_rescaling_on) {
|
||||||
|
@ -485,6 +481,8 @@ void TextureCacheRuntime::Init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextureCacheRuntime::~TextureCacheRuntime() = default;
|
||||||
|
|
||||||
void TextureCacheRuntime::Finish() {
|
void TextureCacheRuntime::Finish() {
|
||||||
glFinish();
|
glFinish();
|
||||||
}
|
}
|
||||||
|
@ -685,6 +683,8 @@ Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {}
|
||||||
|
|
||||||
Image::~Image() = default;
|
Image::~Image() = default;
|
||||||
|
|
||||||
void Image::UploadMemory(const ImageBufferMap& map,
|
void Image::UploadMemory(const ImageBufferMap& map,
|
||||||
|
@ -1076,7 +1076,7 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
|
||||||
const VideoCommon::ImageViewInfo& view_info)
|
const VideoCommon::ImageViewInfo& view_info)
|
||||||
: VideoCommon::ImageViewBase{info, view_info} {}
|
: VideoCommon::ImageViewBase{info, view_info} {}
|
||||||
|
|
||||||
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageParams& params)
|
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
|
||||||
: VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
|
: VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
|
||||||
|
|
||||||
GLuint ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) {
|
GLuint ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) {
|
||||||
|
|
|
@ -73,8 +73,6 @@ public:
|
||||||
StateTracker& state_tracker);
|
StateTracker& state_tracker);
|
||||||
~TextureCacheRuntime();
|
~TextureCacheRuntime();
|
||||||
|
|
||||||
void Init();
|
|
||||||
|
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
ImageBufferMap UploadStagingBuffer(size_t size);
|
ImageBufferMap UploadStagingBuffer(size_t size);
|
||||||
|
@ -167,6 +165,7 @@ class Image : public VideoCommon::ImageBase {
|
||||||
public:
|
public:
|
||||||
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
||||||
VAddr cpu_addr);
|
VAddr cpu_addr);
|
||||||
|
explicit Image(const VideoCommon::NullImageParams&);
|
||||||
|
|
||||||
~Image();
|
~Image();
|
||||||
|
|
||||||
|
@ -223,7 +222,7 @@ public:
|
||||||
const VideoCommon::ImageViewInfo&, GPUVAddr);
|
const VideoCommon::ImageViewInfo&, GPUVAddr);
|
||||||
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
|
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
|
||||||
const VideoCommon::ImageViewInfo& view_info);
|
const VideoCommon::ImageViewInfo& view_info);
|
||||||
explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
|
explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams&);
|
||||||
|
|
||||||
[[nodiscard]] GLuint StorageView(Shader::TextureType texture_type,
|
[[nodiscard]] GLuint StorageView(Shader::TextureType texture_type,
|
||||||
Shader::ImageFormat image_format);
|
Shader::ImageFormat image_format);
|
||||||
|
|
|
@ -156,28 +156,27 @@ private:
|
||||||
u32 texture_bit{1u};
|
u32 texture_bit{1u};
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void PushImageDescriptors(const Shader::Info& info, const VkSampler*& samplers,
|
inline void PushImageDescriptors(TextureCache& texture_cache,
|
||||||
const ImageId*& image_view_ids, TextureCache& texture_cache,
|
|
||||||
VKUpdateDescriptorQueue& update_descriptor_queue,
|
VKUpdateDescriptorQueue& update_descriptor_queue,
|
||||||
RescalingPushConstant& rescaling) {
|
const Shader::Info& info, RescalingPushConstant& rescaling,
|
||||||
static constexpr VideoCommon::ImageViewId NULL_IMAGE_VIEW_ID{0};
|
const VkSampler*& samplers,
|
||||||
image_view_ids += Shader::NumDescriptors(info.texture_buffer_descriptors);
|
const VideoCommon::ImageViewInOut*& views) {
|
||||||
image_view_ids += Shader::NumDescriptors(info.image_buffer_descriptors);
|
views += Shader::NumDescriptors(info.texture_buffer_descriptors);
|
||||||
|
views += Shader::NumDescriptors(info.image_buffer_descriptors);
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const VideoCommon::ImageViewId image_view_id{*(image_view_ids++)};
|
const VideoCommon::ImageViewId image_view_id{(views++)->id};
|
||||||
const VkSampler sampler{*(samplers++)};
|
const VkSampler sampler{*(samplers++)};
|
||||||
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
||||||
const Image& image{texture_cache.GetImage(image_view.image_id)};
|
const Image& image{texture_cache.GetImage(image_view.image_id)};
|
||||||
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
||||||
update_descriptor_queue.AddSampledImage(vk_image_view, sampler);
|
update_descriptor_queue.AddSampledImage(vk_image_view, sampler);
|
||||||
rescaling.PushTexture(image_view_id != NULL_IMAGE_VIEW_ID &&
|
rescaling.PushTexture(True(image.flags & VideoCommon::ImageFlagBits::Rescaled));
|
||||||
True(image.flags & VideoCommon::ImageFlagBits::Rescaled));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
ImageView& image_view{texture_cache.GetImageView(*(image_view_ids++))};
|
ImageView& image_view{texture_cache.GetImageView((views++)->id)};
|
||||||
if (desc.is_written) {
|
if (desc.is_written) {
|
||||||
texture_cache.MarkModification(image_view.image_id);
|
texture_cache.MarkModification(image_view.image_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,10 +108,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
texture_cache.SynchronizeComputeDescriptors();
|
texture_cache.SynchronizeComputeDescriptors();
|
||||||
|
|
||||||
static constexpr size_t max_elements = 64;
|
static constexpr size_t max_elements = 64;
|
||||||
std::array<ImageId, max_elements> image_view_ids;
|
boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views;
|
||||||
boost::container::static_vector<u32, max_elements> image_view_indices;
|
|
||||||
boost::container::static_vector<VkSampler, max_elements> samplers;
|
boost::container::static_vector<VkSampler, max_elements> samplers;
|
||||||
boost::container::static_vector<bool, max_elements> image_view_blacklist;
|
|
||||||
|
|
||||||
const auto& qmd{kepler_compute.launch_description};
|
const auto& qmd{kepler_compute.launch_description};
|
||||||
const auto& cbufs{qmd.const_buffer_config};
|
const auto& cbufs{qmd.const_buffer_config};
|
||||||
|
@ -135,54 +133,37 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
}
|
}
|
||||||
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
|
||||||
}};
|
}};
|
||||||
const auto add_image{[&](const auto& desc) {
|
const auto add_image{[&](const auto& desc, bool blacklist) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices.push_back(handle.first);
|
views.push_back({
|
||||||
|
.index = handle.first,
|
||||||
|
.blacklist = blacklist,
|
||||||
|
.id = {},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
std::ranges::for_each(info.texture_buffer_descriptors, add_image);
|
for (const auto& desc : info.texture_buffer_descriptors) {
|
||||||
std::ranges::for_each(info.image_buffer_descriptors, add_image);
|
add_image(desc, false);
|
||||||
|
}
|
||||||
|
for (const auto& desc : info.image_buffer_descriptors) {
|
||||||
|
add_image(desc, false);
|
||||||
|
}
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices.push_back(handle.first);
|
views.push_back({handle.first});
|
||||||
|
|
||||||
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
Sampler* const sampler = texture_cache.GetComputeSampler(handle.second);
|
||||||
samplers.push_back(sampler->Handle());
|
samplers.push_back(sampler->Handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const u32 black_list_base = image_view_indices.size();
|
|
||||||
bool atleast_one_blacklisted = false;
|
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
const bool is_black_listed =
|
add_image(desc, true);
|
||||||
desc.is_written && (desc.type == Shader::TextureType::Color2D ||
|
|
||||||
desc.type == Shader::TextureType::ColorArray2D);
|
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
|
||||||
image_view_blacklist.push_back(is_black_listed);
|
|
||||||
}
|
}
|
||||||
atleast_one_blacklisted |= is_black_listed;
|
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
|
||||||
add_image(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::span indices_span(image_view_indices.data(), image_view_indices.size());
|
|
||||||
bool has_listed_stuffs;
|
|
||||||
do {
|
|
||||||
has_listed_stuffs = false;
|
|
||||||
texture_cache.FillComputeImageViews(indices_span, image_view_ids);
|
|
||||||
if (atleast_one_blacklisted) {
|
|
||||||
for (u32 index = 0; index < image_view_blacklist.size(); index++) {
|
|
||||||
if (image_view_blacklist[index]) {
|
|
||||||
ImageView& image_view{
|
|
||||||
texture_cache.GetImageView(image_view_ids[index + black_list_base])};
|
|
||||||
has_listed_stuffs |= texture_cache.BlackListImage(image_view.image_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (has_listed_stuffs);
|
|
||||||
|
|
||||||
buffer_cache.UnbindComputeTextureBuffers();
|
buffer_cache.UnbindComputeTextureBuffers();
|
||||||
ImageId* texture_buffer_ids{image_view_ids.data()};
|
|
||||||
size_t index{};
|
size_t index{};
|
||||||
const auto add_buffer{[&](const auto& desc) {
|
const auto add_buffer{[&](const auto& desc) {
|
||||||
constexpr bool is_image = std::is_same_v<decltype(desc), const ImageBufferDescriptor&>;
|
constexpr bool is_image = std::is_same_v<decltype(desc), const ImageBufferDescriptor&>;
|
||||||
|
@ -191,11 +172,10 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
if constexpr (is_image) {
|
if constexpr (is_image) {
|
||||||
is_written = desc.is_written;
|
is_written = desc.is_written;
|
||||||
}
|
}
|
||||||
ImageView& image_view = texture_cache.GetImageView(*texture_buffer_ids);
|
ImageView& image_view = texture_cache.GetImageView(views[index].id);
|
||||||
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
|
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
|
||||||
image_view.BufferSize(), image_view.format,
|
image_view.BufferSize(), image_view.format,
|
||||||
is_written, is_image);
|
is_written, is_image);
|
||||||
++texture_buffer_ids;
|
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
@ -207,9 +187,9 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||||
|
|
||||||
RescalingPushConstant rescaling(num_textures);
|
RescalingPushConstant rescaling(num_textures);
|
||||||
const VkSampler* samplers_it{samplers.data()};
|
const VkSampler* samplers_it{samplers.data()};
|
||||||
const ImageId* views_it{image_view_ids.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
PushImageDescriptors(info, samplers_it, views_it, texture_cache, update_descriptor_queue,
|
PushImageDescriptors(texture_cache, update_descriptor_queue, info, rescaling, samplers_it,
|
||||||
rescaling);
|
views_it);
|
||||||
|
|
||||||
if (!is_built.load(std::memory_order::relaxed)) {
|
if (!is_built.load(std::memory_order::relaxed)) {
|
||||||
// Wait for the pipeline to be built
|
// Wait for the pipeline to be built
|
||||||
|
|
|
@ -278,12 +278,10 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
|
||||||
|
|
||||||
template <typename Spec>
|
template <typename Spec>
|
||||||
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
std::array<ImageId, MAX_IMAGE_ELEMENTS> image_view_ids;
|
std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
|
||||||
std::array<u32, MAX_IMAGE_ELEMENTS> image_view_indices;
|
|
||||||
std::array<bool, MAX_IMAGE_ELEMENTS> image_view_blacklist;
|
|
||||||
std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers;
|
std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers;
|
||||||
size_t sampler_index{};
|
size_t sampler_index{};
|
||||||
size_t image_index{};
|
size_t view_index{};
|
||||||
|
|
||||||
texture_cache.SynchronizeGraphicsDescriptors();
|
texture_cache.SynchronizeGraphicsDescriptors();
|
||||||
|
|
||||||
|
@ -291,8 +289,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
|
|
||||||
const auto& regs{maxwell3d.regs};
|
const auto& regs{maxwell3d.regs};
|
||||||
const bool via_header_index{regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex};
|
const bool via_header_index{regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex};
|
||||||
u32 start_black_list = std::numeric_limits<u32>::max();
|
|
||||||
u32 end_black_list = 0;
|
|
||||||
const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
||||||
const Shader::Info& info{stage_infos[stage]};
|
const Shader::Info& info{stage_infos[stage]};
|
||||||
buffer_cache.UnbindGraphicsStorageBuffers(stage);
|
buffer_cache.UnbindGraphicsStorageBuffers(stage);
|
||||||
|
@ -329,7 +325,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
const auto add_image{[&](const auto& desc) {
|
const auto add_image{[&](const auto& desc) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices[image_index++] = handle.first;
|
views[view_index++] = {handle.first};
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
if constexpr (Spec::has_texture_buffers) {
|
if constexpr (Spec::has_texture_buffers) {
|
||||||
|
@ -345,7 +341,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
for (const auto& desc : info.texture_descriptors) {
|
for (const auto& desc : info.texture_descriptors) {
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
for (u32 index = 0; index < desc.count; ++index) {
|
||||||
const auto handle{read_handle(desc, index)};
|
const auto handle{read_handle(desc, index)};
|
||||||
image_view_indices[image_index++] = handle.first;
|
views[view_index++] = {handle.first};
|
||||||
|
|
||||||
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)};
|
||||||
samplers[sampler_index++] = sampler->Handle();
|
samplers[sampler_index++] = sampler->Handle();
|
||||||
|
@ -353,15 +349,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
}
|
}
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
for (const auto& desc : info.image_descriptors) {
|
for (const auto& desc : info.image_descriptors) {
|
||||||
if (desc.is_written && (desc.type == Shader::TextureType::Color2D ||
|
|
||||||
desc.type == Shader::TextureType::ColorArray2D)) {
|
|
||||||
auto index_copy = image_index;
|
|
||||||
for (u32 index = 0; index < desc.count; ++index) {
|
|
||||||
start_black_list = std::min<u32>(start_black_list, index_copy);
|
|
||||||
image_view_blacklist[index_copy++] = true;
|
|
||||||
end_black_list = std::max<u32>(end_black_list, index_copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add_image(desc);
|
add_image(desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,24 +368,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
if constexpr (Spec::enabled_stages[4]) {
|
if constexpr (Spec::enabled_stages[4]) {
|
||||||
config_stage(4);
|
config_stage(4);
|
||||||
}
|
}
|
||||||
const std::span indices_span(image_view_indices.data(), image_index);
|
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), view_index));
|
||||||
bool has_listed_stuffs;
|
|
||||||
do {
|
|
||||||
has_listed_stuffs = false;
|
|
||||||
texture_cache.FillGraphicsImageViews(indices_span, image_view_ids);
|
|
||||||
if constexpr (Spec::has_images) {
|
|
||||||
if (start_black_list < end_black_list) {
|
|
||||||
for (u32 index = start_black_list; index < end_black_list; index++) {
|
|
||||||
if (image_view_blacklist[index]) {
|
|
||||||
ImageView& image_view{texture_cache.GetImageView(image_view_ids[index])};
|
|
||||||
has_listed_stuffs |= texture_cache.BlackListImage(image_view.image_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (has_listed_stuffs);
|
|
||||||
|
|
||||||
ImageId* texture_buffer_index{image_view_ids.data()};
|
VideoCommon::ImageViewInOut* texture_buffer_it{views.data()};
|
||||||
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
|
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
|
||||||
size_t index{};
|
size_t index{};
|
||||||
const auto add_buffer{[&](const auto& desc) {
|
const auto add_buffer{[&](const auto& desc) {
|
||||||
|
@ -408,12 +380,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
if constexpr (is_image) {
|
if constexpr (is_image) {
|
||||||
is_written = desc.is_written;
|
is_written = desc.is_written;
|
||||||
}
|
}
|
||||||
ImageView& image_view{texture_cache.GetImageView(*texture_buffer_index)};
|
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||||
image_view.BufferSize(), image_view.format,
|
image_view.BufferSize(), image_view.format,
|
||||||
is_written, is_image);
|
is_written, is_image);
|
||||||
++index;
|
++index;
|
||||||
++texture_buffer_index;
|
++texture_buffer_it;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
buffer_cache.UnbindGraphicsTextureBuffers(stage);
|
buffer_cache.UnbindGraphicsTextureBuffers(stage);
|
||||||
|
@ -429,9 +401,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
add_buffer(desc);
|
add_buffer(desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
texture_buffer_index += Shader::NumDescriptors(info.texture_descriptors);
|
texture_buffer_it += Shader::NumDescriptors(info.texture_descriptors);
|
||||||
if constexpr (Spec::has_images) {
|
if constexpr (Spec::has_images) {
|
||||||
texture_buffer_index += Shader::NumDescriptors(info.image_descriptors);
|
texture_buffer_it += Shader::NumDescriptors(info.image_descriptors);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
if constexpr (Spec::enabled_stages[0]) {
|
if constexpr (Spec::enabled_stages[0]) {
|
||||||
|
@ -457,11 +429,11 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||||
|
|
||||||
RescalingPushConstant rescaling(num_textures);
|
RescalingPushConstant rescaling(num_textures);
|
||||||
const VkSampler* samplers_it{samplers.data()};
|
const VkSampler* samplers_it{samplers.data()};
|
||||||
const ImageId* views_it{image_view_ids.data()};
|
const VideoCommon::ImageViewInOut* views_it{views.data()};
|
||||||
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
|
||||||
buffer_cache.BindHostStageBuffers(stage);
|
buffer_cache.BindHostStageBuffers(stage);
|
||||||
PushImageDescriptors(stage_infos[stage], samplers_it, views_it, texture_cache,
|
PushImageDescriptors(texture_cache, update_descriptor_queue, stage_infos[stage], rescaling,
|
||||||
update_descriptor_queue, rescaling);
|
samplers_it, views_it);
|
||||||
}};
|
}};
|
||||||
if constexpr (Spec::enabled_stages[0]) {
|
if constexpr (Spec::enabled_stages[0]) {
|
||||||
prepare_stage(0);
|
prepare_stage(0);
|
||||||
|
|
|
@ -730,10 +730,17 @@ void BlitScale(VKScheduler& scheduler, VkImage src_image, VkImage dst_image, con
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
void TextureCacheRuntime::Init() {
|
TextureCacheRuntime::TextureCacheRuntime(const Device& device_, VKScheduler& scheduler_,
|
||||||
resolution = Settings::values.resolution_info;
|
MemoryAllocator& memory_allocator_,
|
||||||
is_rescaling_on = resolution.up_scale != 1 || resolution.down_shift != 0;
|
StagingBufferPool& staging_buffer_pool_,
|
||||||
}
|
BlitImageHelper& blit_image_helper_,
|
||||||
|
ASTCDecoderPass& astc_decoder_pass_,
|
||||||
|
RenderPassCache& render_pass_cache_)
|
||||||
|
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
|
||||||
|
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
|
||||||
|
astc_decoder_pass{astc_decoder_pass_}, render_pass_cache{render_pass_cache_},
|
||||||
|
resolution{Settings::values.resolution_info},
|
||||||
|
is_rescaling_on(resolution.up_scale != 1 || resolution.down_shift != 0) {}
|
||||||
|
|
||||||
void TextureCacheRuntime::Finish() {
|
void TextureCacheRuntime::Finish() {
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
|
@ -1040,6 +1047,8 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {}
|
||||||
|
|
||||||
Image::~Image() = default;
|
Image::~Image() = default;
|
||||||
|
|
||||||
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
|
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
|
||||||
|
@ -1187,8 +1196,7 @@ bool Image::ScaleDown(bool save_as_backup) {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
const auto& resolution = runtime->resolution;
|
const auto& resolution = runtime->resolution;
|
||||||
vk::Image downscaled_image =
|
vk::Image downscaled_image = MakeImage(runtime->device, info);
|
||||||
MakeImage(runtime->device, info);
|
|
||||||
MemoryCommit new_commit(
|
MemoryCommit new_commit(
|
||||||
runtime->memory_allocator.Commit(downscaled_image, MemoryUsage::DeviceLocal));
|
runtime->memory_allocator.Commit(downscaled_image, MemoryUsage::DeviceLocal));
|
||||||
|
|
||||||
|
@ -1301,7 +1309,7 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
|
||||||
: VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_},
|
: VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_},
|
||||||
buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
|
buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
|
||||||
|
|
||||||
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams& params)
|
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params)
|
||||||
: VideoCommon::ImageViewBase{params} {}
|
: VideoCommon::ImageViewBase{params} {}
|
||||||
|
|
||||||
VkImageView ImageView::DepthView() {
|
VkImageView ImageView::DepthView() {
|
||||||
|
|
|
@ -34,21 +34,16 @@ class RenderPassCache;
|
||||||
class StagingBufferPool;
|
class StagingBufferPool;
|
||||||
class VKScheduler;
|
class VKScheduler;
|
||||||
|
|
||||||
struct TextureCacheRuntime {
|
class TextureCacheRuntime {
|
||||||
const Device& device;
|
public:
|
||||||
VKScheduler& scheduler;
|
|
||||||
MemoryAllocator& memory_allocator;
|
|
||||||
StagingBufferPool& staging_buffer_pool;
|
|
||||||
BlitImageHelper& blit_image_helper;
|
|
||||||
ASTCDecoderPass& astc_decoder_pass;
|
|
||||||
RenderPassCache& render_pass_cache;
|
|
||||||
static constexpr size_t TICKS_TO_DESTROY = 6;
|
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||||
DelayedDestructionRing<vk::Image, TICKS_TO_DESTROY> prescaled_images;
|
|
||||||
DelayedDestructionRing<MemoryCommit, TICKS_TO_DESTROY> prescaled_commits;
|
|
||||||
Settings::ResolutionScalingInfo resolution;
|
|
||||||
bool is_rescaling_on{};
|
|
||||||
|
|
||||||
void Init();
|
explicit TextureCacheRuntime(const Device& device_, VKScheduler& scheduler_,
|
||||||
|
MemoryAllocator& memory_allocator_,
|
||||||
|
StagingBufferPool& staging_buffer_pool_,
|
||||||
|
BlitImageHelper& blit_image_helper_,
|
||||||
|
ASTCDecoderPass& astc_decoder_pass_,
|
||||||
|
RenderPassCache& render_pass_cache_);
|
||||||
|
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
|
@ -56,6 +51,10 @@ struct TextureCacheRuntime {
|
||||||
|
|
||||||
StagingBufferRef DownloadStagingBuffer(size_t size);
|
StagingBufferRef DownloadStagingBuffer(size_t size);
|
||||||
|
|
||||||
|
void TickFrame();
|
||||||
|
|
||||||
|
u64 GetDeviceLocalMemory() const;
|
||||||
|
|
||||||
void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
|
void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
|
||||||
const Region2D& dst_region, const Region2D& src_region,
|
const Region2D& dst_region, const Region2D& src_region,
|
||||||
Tegra::Engines::Fermi2D::Filter filter,
|
Tegra::Engines::Fermi2D::Filter filter,
|
||||||
|
@ -84,15 +83,25 @@ struct TextureCacheRuntime {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TickFrame();
|
const Device& device;
|
||||||
|
VKScheduler& scheduler;
|
||||||
|
MemoryAllocator& memory_allocator;
|
||||||
|
StagingBufferPool& staging_buffer_pool;
|
||||||
|
BlitImageHelper& blit_image_helper;
|
||||||
|
ASTCDecoderPass& astc_decoder_pass;
|
||||||
|
RenderPassCache& render_pass_cache;
|
||||||
|
|
||||||
u64 GetDeviceLocalMemory() const;
|
DelayedDestructionRing<vk::Image, TICKS_TO_DESTROY> prescaled_images;
|
||||||
|
DelayedDestructionRing<MemoryCommit, TICKS_TO_DESTROY> prescaled_commits;
|
||||||
|
Settings::ResolutionScalingInfo resolution;
|
||||||
|
bool is_rescaling_on{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Image : public VideoCommon::ImageBase {
|
class Image : public VideoCommon::ImageBase {
|
||||||
public:
|
public:
|
||||||
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
||||||
VAddr cpu_addr);
|
VAddr cpu_addr);
|
||||||
|
explicit Image(const VideoCommon::NullImageParams&);
|
||||||
|
|
||||||
~Image();
|
~Image();
|
||||||
|
|
||||||
|
@ -151,7 +160,7 @@ public:
|
||||||
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
|
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
|
||||||
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&,
|
explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&,
|
||||||
const VideoCommon::ImageViewInfo&, GPUVAddr);
|
const VideoCommon::ImageViewInfo&, GPUVAddr);
|
||||||
explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
|
explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams&);
|
||||||
|
|
||||||
[[nodiscard]] VkImageView DepthView();
|
[[nodiscard]] VkImageView DepthView();
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,8 @@ ImageBase::ImageBase(const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageBase::ImageBase(const NullImageParams&) {}
|
||||||
|
|
||||||
ImageMapView::ImageMapView(GPUVAddr gpu_addr_, VAddr cpu_addr_, size_t size_, ImageId image_id_)
|
ImageMapView::ImageMapView(GPUVAddr gpu_addr_, VAddr cpu_addr_, size_t size_, ImageId image_id_)
|
||||||
: gpu_addr{gpu_addr_}, cpu_addr{cpu_addr_}, size{size_}, image_id{image_id_} {}
|
: gpu_addr{gpu_addr_}, cpu_addr{cpu_addr_}, size{size_}, image_id{image_id_} {}
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,11 @@ struct AliasedImage {
|
||||||
ImageId id;
|
ImageId id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NullImageParams {};
|
||||||
|
|
||||||
struct ImageBase {
|
struct ImageBase {
|
||||||
explicit ImageBase(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
|
explicit ImageBase(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
|
||||||
|
explicit ImageBase(const NullImageParams&);
|
||||||
|
|
||||||
[[nodiscard]] std::optional<SubresourceBase> TryFindBase(GPUVAddr other_addr) const noexcept;
|
[[nodiscard]] std::optional<SubresourceBase> TryFindBase(GPUVAddr other_addr) const noexcept;
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,6 @@ ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_in
|
||||||
ASSERT_MSG(view_info.type == ImageViewType::Buffer, "Expected texture buffer");
|
ASSERT_MSG(view_info.type == ImageViewType::Buffer, "Expected texture buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageViewBase::ImageViewBase(const NullImageParams&) {}
|
ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {}
|
||||||
|
|
||||||
} // namespace VideoCommon
|
} // namespace VideoCommon
|
||||||
|
|
|
@ -15,7 +15,7 @@ using VideoCore::Surface::PixelFormat;
|
||||||
struct ImageViewInfo;
|
struct ImageViewInfo;
|
||||||
struct ImageInfo;
|
struct ImageInfo;
|
||||||
|
|
||||||
struct NullImageParams {};
|
struct NullImageViewParams {};
|
||||||
|
|
||||||
enum class ImageViewFlagBits : u16 {
|
enum class ImageViewFlagBits : u16 {
|
||||||
PreemtiveDownload = 1 << 0,
|
PreemtiveDownload = 1 << 0,
|
||||||
|
@ -28,7 +28,7 @@ struct ImageViewBase {
|
||||||
explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
|
explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
|
||||||
ImageId image_id);
|
ImageId image_id);
|
||||||
explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info);
|
explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info);
|
||||||
explicit ImageViewBase(const NullImageParams&);
|
explicit ImageViewBase(const NullImageViewParams&);
|
||||||
|
|
||||||
[[nodiscard]] bool IsBuffer() const noexcept {
|
[[nodiscard]] bool IsBuffer() const noexcept {
|
||||||
return type == ImageViewType::Buffer;
|
return type == ImageViewType::Buffer;
|
||||||
|
|
|
@ -36,7 +36,6 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
|
||||||
Tegra::MemoryManager& gpu_memory_)
|
Tegra::MemoryManager& gpu_memory_)
|
||||||
: runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
|
: runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
|
||||||
kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} {
|
kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} {
|
||||||
runtime.Init();
|
|
||||||
// Configure null sampler
|
// Configure null sampler
|
||||||
TSCEntry sampler_descriptor{};
|
TSCEntry sampler_descriptor{};
|
||||||
sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear);
|
sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear);
|
||||||
|
@ -46,7 +45,8 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
|
||||||
|
|
||||||
// Make sure the first index is reserved for the null resources
|
// Make sure the first index is reserved for the null resources
|
||||||
// This way the null resource becomes a compile time constant
|
// This way the null resource becomes a compile time constant
|
||||||
void(slot_image_views.insert(runtime, NullImageParams{}));
|
void(slot_images.insert(NullImageParams{}));
|
||||||
|
void(slot_image_views.insert(runtime, NullImageViewParams{}));
|
||||||
void(slot_samplers.insert(runtime, sampler_descriptor));
|
void(slot_samplers.insert(runtime, sampler_descriptor));
|
||||||
|
|
||||||
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
||||||
|
@ -57,7 +57,7 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
|
||||||
critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY);
|
critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY);
|
||||||
minimum_memory = 0;
|
minimum_memory = 0;
|
||||||
} else {
|
} else {
|
||||||
// on OGL we can be more conservatives as the driver takes care.
|
// On OpenGL we can be more conservatives as the driver takes care.
|
||||||
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
||||||
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
||||||
minimum_memory = expected_memory;
|
minimum_memory = expected_memory;
|
||||||
|
@ -135,15 +135,14 @@ void TextureCache<P>::MarkModification(ImageId id) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::FillGraphicsImageViews(std::span<const u32> indices,
|
template <bool has_blacklists>
|
||||||
std::span<ImageViewId> image_view_ids) {
|
void TextureCache<P>::FillGraphicsImageViews(std::span<ImageViewInOut> views) {
|
||||||
FillImageViews(graphics_image_table, graphics_image_view_ids, indices, image_view_ids);
|
FillImageViews<has_blacklists>(graphics_image_table, graphics_image_view_ids, views);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::FillComputeImageViews(std::span<const u32> indices,
|
void TextureCache<P>::FillComputeImageViews(std::span<ImageViewInOut> views) {
|
||||||
std::span<ImageViewId> image_view_ids) {
|
FillImageViews<false>(compute_image_table, compute_image_view_ids, views);
|
||||||
FillImageViews(compute_image_table, compute_image_view_ids, indices, image_view_ids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -346,17 +345,26 @@ typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
template <bool has_blacklists>
|
||||||
void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
|
void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
|
||||||
std::span<ImageViewId> cached_image_view_ids,
|
std::span<ImageViewId> cached_image_view_ids,
|
||||||
std::span<const u32> indices,
|
std::span<ImageViewInOut> views) {
|
||||||
std::span<ImageViewId> image_view_ids) {
|
bool has_blacklisted;
|
||||||
ASSERT(indices.size() <= image_view_ids.size());
|
|
||||||
do {
|
do {
|
||||||
has_deleted_images = false;
|
has_deleted_images = false;
|
||||||
std::ranges::transform(indices, image_view_ids.begin(), [&](u32 index) {
|
if constexpr (has_blacklists) {
|
||||||
return VisitImageView(table, cached_image_view_ids, index);
|
has_blacklisted = false;
|
||||||
});
|
}
|
||||||
} while (has_deleted_images);
|
for (ImageViewInOut& view : views) {
|
||||||
|
view.id = VisitImageView(table, cached_image_view_ids, view.index);
|
||||||
|
if constexpr (has_blacklists) {
|
||||||
|
if (view.blacklist && view.id != NULL_IMAGE_VIEW_ID) {
|
||||||
|
const ImageViewBase& image_view{slot_image_views[view.id]};
|
||||||
|
has_blacklisted |= BlackListImage(image_view.image_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (has_deleted_images || (has_blacklists && has_blacklisted));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -622,7 +630,7 @@ void TextureCache<P>::PopAsyncFlushes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool TextureCache<P>::IsRescaling() {
|
bool TextureCache<P>::IsRescaling() const noexcept {
|
||||||
return is_rescaling;
|
return is_rescaling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,12 +783,11 @@ bool TextureCache<P>::BlackListImage(ImageId image_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool TextureCache<P>::ImageCanRescale(Image& image) {
|
bool TextureCache<P>::ImageCanRescale(ImageBase& image) {
|
||||||
if (True(image.flags & ImageFlagBits::Blacklisted)) {
|
if (True(image.flags & ImageFlagBits::Blacklisted)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (True(image.flags & ImageFlagBits::Rescaled) ||
|
if (True(image.flags & (ImageFlagBits::Rescaled | ImageFlagBits::RescaleChecked))) {
|
||||||
True(image.flags & ImageFlagBits::RescaleChecked)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!image.info.rescaleable) {
|
if (!image.info.rescaleable) {
|
||||||
|
|
|
@ -39,6 +39,16 @@ using VideoCore::Surface::PixelFormatFromDepthFormat;
|
||||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
|
|
||||||
|
struct ImageViewInOut {
|
||||||
|
u32 index;
|
||||||
|
bool blacklist;
|
||||||
|
union {
|
||||||
|
struct Empty {
|
||||||
|
} empty{};
|
||||||
|
ImageViewId id;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
class TextureCache {
|
class TextureCache {
|
||||||
/// Address shift for caching images into a hash table
|
/// Address shift for caching images into a hash table
|
||||||
|
@ -53,11 +63,6 @@ class TextureCache {
|
||||||
/// True when the API can provide info about the memory of the device.
|
/// True when the API can provide info about the memory of the device.
|
||||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
|
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
|
||||||
|
|
||||||
/// Image view ID for null descriptors
|
|
||||||
static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0};
|
|
||||||
/// Sampler ID for bugged sampler ids
|
|
||||||
static constexpr SamplerId NULL_SAMPLER_ID{0};
|
|
||||||
|
|
||||||
static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB;
|
static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB;
|
||||||
static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB;
|
static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB;
|
||||||
|
|
||||||
|
@ -105,11 +110,11 @@ public:
|
||||||
void MarkModification(ImageId id) noexcept;
|
void MarkModification(ImageId id) noexcept;
|
||||||
|
|
||||||
/// Fill image_view_ids with the graphics images in indices
|
/// Fill image_view_ids with the graphics images in indices
|
||||||
void FillGraphicsImageViews(std::span<const u32> indices,
|
template <bool has_blacklists>
|
||||||
std::span<ImageViewId> image_view_ids);
|
void FillGraphicsImageViews(std::span<ImageViewInOut> views);
|
||||||
|
|
||||||
/// Fill image_view_ids with the compute images in indices
|
/// Fill image_view_ids with the compute images in indices
|
||||||
void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids);
|
void FillComputeImageViews(std::span<ImageViewInOut> views);
|
||||||
|
|
||||||
/// Get the sampler from the graphics descriptor table in the specified index
|
/// Get the sampler from the graphics descriptor table in the specified index
|
||||||
Sampler* GetGraphicsSampler(u32 index);
|
Sampler* GetGraphicsSampler(u32 index);
|
||||||
|
@ -174,7 +179,7 @@ public:
|
||||||
/// Return true when a CPU region is modified from the GPU
|
/// Return true when a CPU region is modified from the GPU
|
||||||
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
|
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
|
||||||
|
|
||||||
[[nodiscard]] bool IsRescaling();
|
[[nodiscard]] bool IsRescaling() const noexcept;
|
||||||
|
|
||||||
[[nodiscard]] bool BlackListImage(ImageId image_id);
|
[[nodiscard]] bool BlackListImage(ImageId image_id);
|
||||||
|
|
||||||
|
@ -216,9 +221,10 @@ private:
|
||||||
void RunGarbageCollector();
|
void RunGarbageCollector();
|
||||||
|
|
||||||
/// Fills image_view_ids in the image views in indices
|
/// Fills image_view_ids in the image views in indices
|
||||||
|
template <bool has_blacklists>
|
||||||
void FillImageViews(DescriptorTable<TICEntry>& table,
|
void FillImageViews(DescriptorTable<TICEntry>& table,
|
||||||
std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices,
|
std::span<ImageViewId> cached_image_view_ids,
|
||||||
std::span<ImageViewId> image_view_ids);
|
std::span<ImageViewInOut> views);
|
||||||
|
|
||||||
/// Find or create an image view in the guest descriptor table
|
/// Find or create an image view in the guest descriptor table
|
||||||
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
|
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
|
||||||
|
@ -336,7 +342,7 @@ private:
|
||||||
/// Returns true if the current clear parameters clear the whole image of a given image view
|
/// Returns true if the current clear parameters clear the whole image of a given image view
|
||||||
[[nodiscard]] bool IsFullClear(ImageViewId id);
|
[[nodiscard]] bool IsFullClear(ImageViewId id);
|
||||||
|
|
||||||
bool ImageCanRescale(Image& image);
|
bool ImageCanRescale(ImageBase& image);
|
||||||
void InvalidateScale(Image& image);
|
void InvalidateScale(Image& image);
|
||||||
bool ScaleUp(Image& image);
|
bool ScaleUp(Image& image);
|
||||||
bool ScaleDown(Image& image);
|
bool ScaleDown(Image& image);
|
||||||
|
|
|
@ -22,6 +22,13 @@ using ImageAllocId = SlotId;
|
||||||
using SamplerId = SlotId;
|
using SamplerId = SlotId;
|
||||||
using FramebufferId = SlotId;
|
using FramebufferId = SlotId;
|
||||||
|
|
||||||
|
/// Fake image ID for null image views
|
||||||
|
constexpr ImageId NULL_IMAGE_ID{0};
|
||||||
|
/// Image view ID for null descriptors
|
||||||
|
constexpr ImageViewId NULL_IMAGE_VIEW_ID{0};
|
||||||
|
/// Sampler ID for bugged sampler ids
|
||||||
|
constexpr SamplerId NULL_SAMPLER_ID{0};
|
||||||
|
|
||||||
enum class ImageType : u32 {
|
enum class ImageType : u32 {
|
||||||
e1D,
|
e1D,
|
||||||
e2D,
|
e2D,
|
||||||
|
|
Loading…
Reference in a new issue