rpcsx/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp

213 lines
7.3 KiB
C++
Raw Normal View History

2022-12-17 10:52:17 +01:00
#include "barriers.h"
#include "buffer_object.h"
#include "image.h"
#include "../VKResourceManager.h"
2025-10-05 18:28:03 +02:00
#include <rx/align.hpp>
#include <rx/asm.hpp>
namespace vk
{
std::unordered_map<VkImageViewType, std::unique_ptr<viewable_image>> g_null_image_views;
std::unordered_map<u32, std::unique_ptr<image>> g_typeless_textures;
VkSampler g_null_sampler = nullptr;
// Scratch memory handling. Use double-buffered resource to significantly cut down on GPU stalls
struct scratch_buffer_pool_t
{
std::array<std::unique_ptr<buffer>, 2> scratch_buffers;
u32 current_index = 0;
std::unique_ptr<buffer>& get_buf()
{
auto& ret = scratch_buffers[current_index];
current_index ^= 1;
return ret;
}
};
std::unordered_map<u32, scratch_buffer_pool_t> g_scratch_buffers_pool;
VkSampler null_sampler()
{
if (g_null_sampler)
return g_null_sampler;
VkSamplerCreateInfo sampler_info = {};
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_info.anisotropyEnable = VK_FALSE;
sampler_info.compareEnable = VK_FALSE;
sampler_info.unnormalizedCoordinates = VK_FALSE;
sampler_info.mipLodBias = 0;
sampler_info.maxAnisotropy = 0;
sampler_info.magFilter = VK_FILTER_NEAREST;
sampler_info.minFilter = VK_FILTER_NEAREST;
sampler_info.compareOp = VK_COMPARE_OP_NEVER;
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
VK_GET_SYMBOL(vkCreateSampler)(*g_render_device, &sampler_info, nullptr, &g_null_sampler);
return g_null_sampler;
}
vk::image_view* null_image_view(const vk::command_buffer& cmd, VkImageViewType type)
{
if (auto found = g_null_image_views.find(type);
found != g_null_image_views.end())
{
return found->second->get_view(rsx::default_remap_vector.with_encoding(VK_REMAP_IDENTITY));
}
VkImageType image_type;
u32 num_layers = 1;
u32 flags = 0;
u16 size = 4;
switch (type)
{
case VK_IMAGE_VIEW_TYPE_1D:
image_type = VK_IMAGE_TYPE_1D;
size = 1;
break;
case VK_IMAGE_VIEW_TYPE_2D:
image_type = VK_IMAGE_TYPE_2D;
break;
case VK_IMAGE_VIEW_TYPE_3D:
image_type = VK_IMAGE_TYPE_3D;
break;
case VK_IMAGE_VIEW_TYPE_CUBE:
image_type = VK_IMAGE_TYPE_2D;
flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
num_layers = 6;
break;
case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
image_type = VK_IMAGE_TYPE_2D;
num_layers = 2;
break;
default:
rsx_log.fatal("Unexpected image view type 0x%x", static_cast<u32>(type));
return nullptr;
}
auto& tex = g_null_image_views[type];
tex = std::make_unique<viewable_image>(*g_render_device, g_render_device->get_memory_mapping().device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
image_type, VK_FORMAT_B8G8R8A8_UNORM, size, size, 1, 1, num_layers, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, flags | VK_IMAGE_CREATE_ALLOW_NULL_RPCS3, VMM_ALLOCATION_POOL_SCRATCH);
if (!tex->value)
{
// If we cannot create a 1x1 placeholder, things are truly hopeless.
// The null view is 'nullable' because it is meant for use in emergency situations and we do not wish to invalidate any handles.
fmt::throw_exception("Renderer is out of memory. We could not even squeeze in a 1x1 texture, things are bad.");
}
// Initialize memory to transparent black
tex->change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkClearColorValue clear_color = {};
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, tex->mipmaps(), 0, tex->layers()};
VK_GET_SYMBOL(vkCmdClearColorImage)(cmd, tex->value, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &range);
// Prep for shader access
tex->change_layout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// Return view
return tex->get_view(rsx::default_remap_vector.with_encoding(VK_REMAP_IDENTITY));
}
vk::image* get_typeless_helper(VkFormat format, rsx::format_class format_class, u32 requested_width, u32 requested_height)
{
auto create_texture = [&]()
{
2025-10-05 18:28:03 +02:00
u32 new_width = rx::alignUp(requested_width, 256u);
u32 new_height = rx::alignUp(requested_height, 256u);
return new vk::image(*g_render_device, g_render_device->get_memory_mapping().device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
VK_IMAGE_TYPE_2D, format, new_width, new_height, 1, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, 0, VMM_ALLOCATION_POOL_SCRATCH,
format_class);
};
const u32 key = (format_class << 24u) | format;
auto& ptr = g_typeless_textures[key];
if (!ptr || ptr->width() < requested_width || ptr->height() < requested_height)
{
if (ptr)
{
requested_width = std::max(requested_width, ptr->width());
requested_height = std::max(requested_height, ptr->height());
get_resource_manager()->dispose(ptr);
}
ptr.reset(create_texture());
2022-08-05 19:05:04 +02:00
ptr->set_debug_name(fmt::format("Scratch: Format=0x%x", static_cast<u32>(format)));
}
return ptr.get();
}
std::pair<vk::buffer*, bool> get_scratch_buffer(u32 queue_family, u64 min_required_size)
{
auto& scratch_buffer = g_scratch_buffers_pool[queue_family].get_buf();
bool is_new = false;
if (scratch_buffer && scratch_buffer->size() < min_required_size)
{
// Scratch heap cannot fit requirements. Discard it and allocate a new one.
vk::get_resource_manager()->dispose(scratch_buffer);
}
if (!scratch_buffer)
{
// Choose optimal size
2025-10-05 18:28:03 +02:00
const u64 alloc_size = rx::alignUp(min_required_size, 0x100000);
scratch_buffer = std::make_unique<vk::buffer>(*g_render_device, alloc_size,
g_render_device->get_memory_mapping().device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 0, VMM_ALLOCATION_POOL_SCRATCH);
is_new = true;
}
return {scratch_buffer.get(), is_new};
}
vk::buffer* get_scratch_buffer(const vk::command_buffer& cmd, u64 min_required_size, bool zero_memory)
{
const auto [buf, init_mem] = get_scratch_buffer(cmd.get_queue_family(), min_required_size);
if (init_mem || zero_memory)
{
// Zero-initialize the allocated VRAM
2025-10-05 18:28:03 +02:00
const u64 zero_length = init_mem ? buf->size() : rx::alignUp(min_required_size, 4);
VK_GET_SYMBOL(vkCmdFillBuffer)(cmd, buf->value, 0, zero_length, 0);
2022-12-17 10:52:17 +01:00
insert_buffer_memory_barrier(cmd, buf->value, 0, zero_length,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
}
return buf;
}
void clear_scratch_resources()
{
g_null_image_views.clear();
g_scratch_buffers_pool.clear();
g_typeless_textures.clear();
if (g_null_sampler)
{
VK_GET_SYMBOL(vkDestroySampler)(*g_render_device, g_null_sampler, nullptr);
g_null_sampler = nullptr;
}
}
} // namespace vk