Browse Source

Added ability to toggle the async unswizzle system between Off, CPU, GPU

Restored older GPU unswizzle system that worked better
Added CPU async texture unswizzling for VRAM limited devices
Fixed issue with GPU unswizzle that caused the sparse texture to skip slices
Attempted to save ram by only allocating space for the slices that have data
Improve speed by saving a buffer that doesn't need to be allocated per-sparse texture
Attempt to speed up the CPU unswizzle side by batching to reduce loop counts and using SSE2/AVX2
pull/3737/head
Forrest Mark X 2 days ago
committed by crueter
parent
commit
ce81982887
  1. 10
      src/common/settings.h
  2. 1
      src/common/settings_enums.h
  3. 20
      src/qt_common/config/shared_translation.cpp
  4. 13
      src/video_core/CMakeLists.txt
  5. 3
      src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp
  6. 6
      src/video_core/renderer_opengl/gl_texture_cache.cpp
  7. 8
      src/video_core/renderer_opengl/gl_texture_cache.h
  8. 247
      src/video_core/renderer_vulkan/vk_compute_pass.cpp
  9. 9
      src/video_core/renderer_vulkan/vk_compute_pass.h
  10. 37
      src/video_core/renderer_vulkan/vk_texture_cache.cpp
  11. 6
      src/video_core/renderer_vulkan/vk_texture_cache.h
  12. 78
      src/video_core/texture_cache/accelerated_swizzle.h
  13. 928
      src/video_core/texture_cache/texture_cache.h
  14. 83
      src/video_core/texture_cache/texture_cache_base.h
  15. 133
      src/video_core/textures/decoders.cpp
  16. 157
      src/video_core/textures/decoders_avx2.cpp
  17. 16
      src/video_core/textures/decoders_avx2.h
  18. 131
      src/video_core/textures/decoders_sse2.cpp
  19. 16
      src/video_core/textures/decoders_sse2.h

10
src/common/settings.h

@ -610,8 +610,14 @@ struct Values {
Category::RendererHacks,
Specialization::Default};
SwitchableSetting<bool> gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled",
Category::RendererHacks};
/*SwitchableSetting<bool> gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled",
Category::RendererHacks};*/
SwitchableSetting<AsyncUnswizzleMode> async_unswizzle_mode{linkage,
AsyncUnswizzleMode::Off,
"async_unswizzle_mode",
Category::RendererHacks,
Specialization::Default};
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
#if defined(__ANDROID__)

1
src/common/settings_enums.h

@ -157,6 +157,7 @@ ENUM(GpuClock, Normal, Boost, Overclock)
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High, Off)
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High, Off)
ENUM(AsyncUnswizzleMode, Off, Gpu, Cpu)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)

20
src/qt_common/config/shared_translation.cpp

@ -171,8 +171,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
"In most cases, GPU decoding provides the best performance."));
INSERT(Settings, accelerate_unswizzle, tr("Texture Unsiwzzle Method:"),
tr("This option controls how generic textures should be unswizzled.\n"
"CPU: Use the CPU for unswizzling.\n"
"GPU: Use the GPU's compute shaders to unswizzling generic textures (recommended)."));
"CPU: Use the CPU for unswizzling (recommended).\n"
"GPU: Use the GPU's compute shaders to unswizzling generic textures."));
INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"),
tr("This option controls how ASTC textures should be decoded.\n"
"CPU: Use the CPU for decoding.\n"
@ -234,9 +234,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
"resolution and render distance to fit the Switch's clocks."));
INSERT(Settings, gpu_unswizzle_enabled, tr("Chunked GPU Unswizzle"),
/*INSERT(Settings, gpu_unswizzle_enabled, tr("Chunked GPU Unswizzle"),
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
"Disable if experiencing crashes or graphical glitches."));
"Disable if experiencing crashes or graphical glitches."));*/
INSERT(Settings, gpu_unswizzle_texture_size, tr("GPU Unswizzle Max Texture Size"),
tr("Sets the maximum size (MiB) for GPU-based texture unswizzling.\n"
"While the GPU is faster for medium and large textures, the CPU may be more "
@ -250,6 +250,12 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Determines the number of depth slices processed in a single dispatch.\n"
"Increasing this can improve throughput on high-end GPUs but may cause TDR or driver "
"timeouts on weaker hardware."));
INSERT(Settings, async_unswizzle_mode, tr("Async Unswizzle Mode"),
tr("This option controls how chunked texture uploads are unswizzled.\n"
"Off: Disables asynchronous texture unswizzling.\n"
"CPU: Uses the CPU for asynchronous unswizzling.\n"
"GPU: Accelerates BCn 3D texture decoding using GPU compute (recommended).\n"
"Change to CPU or Off if VRAM is limited."));
INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading "
@ -688,6 +694,12 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(GpuUnswizzleChunk, High, tr("High (512)")),
PAIR(GpuUnswizzleChunk, Off, tr("Off")),
}});
translations->insert({Settings::EnumMetadata<Settings::AsyncUnswizzleMode>::Index(),
{
PAIR(AsyncUnswizzleMode, Off, tr("Off")),
PAIR(AsyncUnswizzleMode, Cpu, tr("CPU")),
PAIR(AsyncUnswizzleMode, Gpu, tr("GPU")),
}});
translations->insert({Settings::EnumMetadata<Settings::ExtendedDynamicState>::Index(),
{

13
src/video_core/CMakeLists.txt

@ -393,6 +393,19 @@ endif()
if (ARCHITECTURE_x86_64)
target_link_libraries(video_core PUBLIC xbyak::xbyak)
target_sources(video_core PRIVATE
textures/decoders_avx2.cpp
textures/decoders_avx2.h
textures/decoders_sse2.cpp
textures/decoders_sse2.h
)
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set_source_files_properties(textures/decoders_avx2.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX2")
else()
set_source_files_properties(textures/decoders_sse2.cpp PROPERTIES COMPILE_OPTIONS "-msse2")
set_source_files_properties(textures/decoders_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2")
endif()
endif()
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)

3
src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp

@ -33,7 +33,7 @@ layout(push_constant) uniform PushConstants {
uint block_depth; // Offset 48
uint block_depth_mask; // Offset 52
int _pad; // Offset 56
uint src_buffer_byte_bias; // Offset 56
ivec3 destination; // Offset 60
} pc;
@ -125,6 +125,7 @@ void main() {
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
offset -= pc.src_buffer_byte_bias;
uvec4 texel = ReadTexel(offset);

6
src/video_core/renderer_opengl/gl_texture_cache.cpp

@ -654,9 +654,11 @@ void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src,
is_linear ? GL_LINEAR : GL_NEAREST);
}
void TextureCacheRuntime::AccelerateImageUpload(Image &image, const StagingBufferMap &map,
void TextureCacheRuntime::AccelerateImageUpload(Image& image, const StagingBufferMap& map,
std::span<const SwizzleParameters> swizzles,
u32 z_src_start, u32 z_image_start) {
u32 z_src_start, u32 z_image_start, u32 z_count,
[[maybe_unused]] std::span<const u8> slice_has_data,
[[maybe_unused]] bool image_already_uploaded) {
switch (image.info.type) {
case ImageType::e2D:
if (IsPixelFormatASTC(image.info.format)) {

8
src/video_core/renderer_opengl/gl_texture_cache.h

@ -126,9 +126,11 @@ public:
const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter,
Tegra::Engines::Fermi2D::Operation operation);
void AccelerateImageUpload(Image &, const StagingBufferMap &,
std::span<const VideoCommon::SwizzleParameters>,
u32 z_src_start, u32 z_image_start);
void AccelerateImageUpload(Image& image, const StagingBufferMap& map,
std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_src_start, u32 z_image_start, u32 z_count,
std::span<const u8> slice_has_data = {},
bool image_already_uploaded = false);
void InsertUploadMemoryBarrier();

247
src/video_core/renderer_vulkan/vk_compute_pass.cpp

@ -1021,7 +1021,7 @@ struct alignas(16) BlockLinearUnswizzle3DPushConstants {
u32 block_depth; // Offset 48
u32 block_depth_mask; // Offset 52
s32 _pad; // Offset 56
u32 src_buffer_byte_bias; // Offset 56
s32 destination[3]; // Offset 60
s32 _pad_end; // Offset 72
@ -1092,18 +1092,14 @@ void BlockLinearUnswizzle3DPass::Unswizzle(
std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_src_start, u32 z_image_start, u32 z_count,
std::span<const u8> slice_has_data,
std::span<const VideoCommon::Accelerated::SliceBBox> slice_bounds,
bool image_already_uploaded)
{
using namespace VideoCommon::Accelerated;
const u32 MAX_BATCH_SLICES = (std::clamp)(z_count, 1u, image.info.size.depth);
static constexpr u32 MAX_WINDOW_SLICES = 4;
static constexpr float AREA_GROWTH_LIMIT = 3.0f;
const u32 MAX_BATCH_SLICES = (std::min)(z_count, image.info.size.depth);
// Removing the if (!image.has_compute_unswizzle_buffer) check here is not ideal but MAX_BATCH_SLICES can changed mid-way through and I don't want to cause device loss or corruption
// This may cause issues so needs thorough testing, if works fine this is a win to reduce vram usage
image.AllocateComputeUnswizzleBuffer((std::min)(MAX_BATCH_SLICES, MAX_WINDOW_SLICES));
image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES);
ASSERT(swizzles.size() == 1);
const auto& sw = swizzles[0];
@ -1118,7 +1114,10 @@ void BlockLinearUnswizzle3DPass::Unswizzle(
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([dst_image = image.Handle(), aspect = image.AspectMask(), initial_prior_layout, level = sw.level](vk::CommandBuffer cmdbuf) {
const u32 src_buffer_byte_bias = (z_src_start < image.slice_offsets.size())
? image.slice_offsets[z_src_start] : 0;
scheduler.Record([dst_image = image.Handle(), aspect = image.AspectMask(), initial_prior_layout](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE) {
return;
}
@ -1138,126 +1137,39 @@ void BlockLinearUnswizzle3DPass::Unswizzle(
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange = {aspect, static_cast<u32>(level), 1, 0, 1},
.subresourceRange = {aspect, 0, 1, 0, 1},
};
cmdbuf.PipelineBarrier(src_stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, pre_barrier);
});
struct Run { bool has_data; u32 start; u32 len; };
boost::container::small_vector<Run, 16> runs;
{
u32 pos = 0;
while (pos < z_count) {
const u32 src = z_src_start + pos;
const bool val = slice_has_data.empty() ||
(src < static_cast<u32>(slice_has_data.size()) && slice_has_data[src] != 0);
u32 len = 1;
while (pos + len < z_count) {
const u32 next_src = z_src_start + pos + len;
const bool next_val = slice_has_data.empty() ||
(next_src < static_cast<u32>(slice_has_data.size()) && slice_has_data[next_src] != 0);
if (next_val != val) break;
++len;
}
runs.push_back({val, pos, len});
pos += len;
}
}
for (const Run& r : runs) {
u32 sub_offset = 0;
while (sub_offset < r.len) {
const u32 sub_len = (std::min)(r.len - sub_offset, MAX_BATCH_SLICES);
const u32 z_src = z_src_start + r.start + sub_offset;
const u32 z_dst = z_image_start + r.start + sub_offset;
if (!r.has_data) {
// Uncomment if junk data appears
//UnswizzleZeroChunk(image, z_dst, sub_len);
sub_offset += sub_len;
continue;
}
// Uncomment if junk data appears
/*UnswizzleZeroChunk(image, z_dst, sub_len);
scheduler.Record([dst_image = image.Handle(), aspect = image.AspectMask()](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE) return;
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange = {aspect, 0, 1, 0, 1},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, barrier);
});*/
u32 win_offset = 0;
while (win_offset < sub_len) {
const u32 wz_start = z_src + win_offset;
const u32 wz_dst = z_dst + win_offset;
u32 win_len = 0;
u32 ux0 = blocks_x, uy0 = blocks_y, ux1 = 0, uy1 = 0;
u64 slice_area_sum = 0;
bool any_in_window = false;
while (win_offset + win_len < sub_len && win_len < MAX_WINDOW_SLICES) {
const u32 z = wz_start + win_len;
const bool have_box = !slice_bounds.empty() &&
z < static_cast<u32>(slice_bounds.size());
const auto b = have_box ? slice_bounds[z] : SliceBBox{};
const bool slice_populated = b.x1 > b.x0 && b.y1 > b.y0;
if (slice_populated) {
const u32 cx0 = any_in_window ? (std::min)(ux0, b.x0) : b.x0;
const u32 cy0 = any_in_window ? (std::min)(uy0, b.y0) : b.y0;
const u32 cx1 = any_in_window ? (std::max)(ux1, b.x1) : b.x1;
const u32 cy1 = any_in_window ? (std::max)(uy1, b.y1) : b.y1;
const u64 candidate_area =
static_cast<u64>(cx1 - cx0) * (cy1 - cy0);
const u64 candidate_slice_sum = slice_area_sum +
static_cast<u64>(b.x1 - b.x0) * (b.y1 - b.y0);
if (any_in_window && static_cast<float>(candidate_area) > (AREA_GROWTH_LIMIT * static_cast<float>(candidate_slice_sum))) {
break;
}
ux0 = cx0; uy0 = cy0; ux1 = cx1; uy1 = cy1;
slice_area_sum = candidate_slice_sum;
any_in_window = true;
}
++win_len;
}
if (win_len == 0) {
win_len = 1;
for (u32 z_offset = 0; z_offset < z_count; z_offset += MAX_BATCH_SLICES) {
const u32 current_chunk_slices = (std::min)(MAX_BATCH_SLICES, z_count - z_offset);
const u32 current_z_src = z_src_start + z_offset;
const u32 current_z_dst = z_image_start + z_offset;
bool chunk_has_data = slice_has_data.empty();
if (!chunk_has_data) {
const u32 z_src_end = current_z_src + current_chunk_slices;
for (u32 z = current_z_src; z < z_src_end; ++z) {
if (z < static_cast<u32>(slice_has_data.size()) && slice_has_data[z] != 0) {
chunk_has_data = true;
break;
}
const u32 ox0 = any_in_window ? ux0 : 0;
const u32 oy0 = any_in_window ? uy0 : 0;
const u32 ox1 = any_in_window ? ux1 : blocks_x;
const u32 oy1 = any_in_window ? uy1 : blocks_y;
UnswizzleChunk(image, swizzled, sw, params,
ox0, oy0, ox1 - ox0, oy1 - oy0,
wz_start, wz_dst, win_len);
win_offset += win_len;
}
sub_offset += sub_len;
}
if (chunk_has_data) {
UnswizzleChunk(image, swizzled, sw, params, blocks_x, blocks_y,
current_z_src, current_z_dst, current_chunk_slices,
src_buffer_byte_bias);
}/* else { // If junk data appears uncomment this
UnswizzleZeroChunk(image, blocks_x, blocks_y, bytes_per_block,
current_z_dst, current_chunk_slices);
}*/
}
scheduler.Record([dst_image = image.Handle(), aspect = image.AspectMask(), level = sw.level](vk::CommandBuffer cmdbuf) {
scheduler.Record([dst_image = image.Handle(), aspect = image.AspectMask()](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE) return;
const VkImageMemoryBarrier post_barrier{
@ -1270,7 +1182,7 @@ void BlockLinearUnswizzle3DPass::Unswizzle(
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange = {aspect, static_cast<u32>(level), 1, 0, 1},
.subresourceRange = {aspect, 0, 1, 0, 1},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
@ -1284,16 +1196,14 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
const StagingBufferRef& swizzled,
const VideoCommon::SwizzleParameters& sw,
const BlockLinearSwizzle3DParams& params,
u32 origin_x, u32 origin_y,
u32 extent_x, u32 extent_y,
u32 z_src, u32 z_dst, u32 z_count)
u32 blocks_x, u32 blocks_y,
u32 z_src, u32 z_dst, u32 z_count,
u32 src_buffer_byte_bias)
{
image.compute_unswizzle_buffer_is_zero = false;
BlockLinearUnswizzle3DPushConstants pc{};
pc.origin[0] = params.origin[0] + origin_x * 4u;
pc.origin[1] = params.origin[1] + origin_y * 4u;
pc.origin[0] = params.origin[0];
pc.origin[1] = params.origin[1];
pc.origin[2] = z_src; // Current chunk's Z start
pc.destination[0] = params.destination[0];
@ -1308,9 +1218,10 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
pc.block_height_mask = params.block_height_mask;
pc.block_depth = params.block_depth;
pc.block_depth_mask = params.block_depth_mask;
pc.src_buffer_byte_bias = src_buffer_byte_bias;
pc.blocks_dim[0] = extent_x;
pc.blocks_dim[1] = extent_y;
pc.blocks_dim[0] = blocks_x;
pc.blocks_dim[1] = blocks_y;
pc.blocks_dim[2] = z_count; // Only process the count
compute_pass_descriptor_queue.Acquire(scheduler, 3);
@ -1323,32 +1234,25 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(extent_x, 8u);
const u32 gy = Common::DivCeil(extent_y, 8u);
const u32 gx = Common::DivCeil(blocks_x, 8u);
const u32 gy = Common::DivCeil(blocks_y, 8u);
const u32 gz = Common::DivCeil(z_count, 4u);
const u32 bytes_per_block = 1u << pc.bytes_per_block_log2;
const VkDeviceSize output_slice_size =
static_cast<VkDeviceSize>(extent_x) * extent_y * bytes_per_block;
static_cast<VkDeviceSize>(blocks_x) * blocks_y * bytes_per_block;
const VkDeviceSize barrier_size = output_slice_size * z_count;
const VkBuffer out_buffer = *image.compute_unswizzle_buffer;
const VkImage dst_image = image.Handle();
const VkImageAspectFlags aspect = image.AspectMask();
const u32 level = sw.level;
const s32 dst_x = static_cast<s32>(origin_x * 4u);
const s32 dst_y = static_cast<s32>(origin_y * 4u);
const VideoCommon::Extent3D level_size = VideoCommon::MipSize(image.info.size, level);
const u32 level_width = (std::max)(level_size.width, 1u);
const u32 level_height = (std::max)(level_size.height, 1u);
const u32 copy_width = (std::min)(extent_x * 4u, level_width - (std::min)(level_width, static_cast<u32>(dst_x)));
const u32 copy_height = (std::min)(extent_y * 4u, level_height - (std::min)(level_height, static_cast<u32>(dst_y)));
const u32 image_width = image.info.size.width;
const u32 image_height = image.info.size.height;
scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz,
z_dst, z_count, barrier_size,
out_buffer, dst_image, aspect, level,
dst_x, dst_y, copy_width, copy_height](vk::CommandBuffer cmdbuf) {
out_buffer, dst_image, aspect,
image_width, image_height](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
@ -1386,6 +1290,7 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
.offset = 0,
.size = barrier_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, buffer_barrier);
// Copy chunk to correct Z position in image
@ -1393,9 +1298,9 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
.bufferOffset = 0, // Read from start of staging buffer
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = {aspect, level, 0, 1},
.imageOffset = {dst_x, dst_y, static_cast<s32>(z_dst)},
.imageExtent = {copy_width, copy_height, z_count},
.imageSubresource = {aspect, 0, 0, 1},
.imageOffset = {0, 0, static_cast<s32>(z_dst)}, // Write to correct Z
.imageExtent = {image_width, image_height, z_count},
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
@ -1502,6 +1407,8 @@ void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
// So enjoy this mess
void BlockLinearUnswizzle3DPass::UnswizzleZeroChunk(
Image& image,
u32 blocks_x, u32 blocks_y,
u32 bytes_per_block,
u32 z_dst, u32 z_count)
{
ASSERT(image.has_compute_unswizzle_buffer);
@ -1511,42 +1418,40 @@ void BlockLinearUnswizzle3DPass::UnswizzleZeroChunk(
const VkImageAspectFlags aspect = image.AspectMask();
const u32 image_width = image.info.size.width;
const u32 image_height = image.info.size.height;
const bool needs_fill = !image.compute_unswizzle_buffer_is_zero;
const VkDeviceSize buffer_capacity = image.compute_unswizzle_buffer_size;
if (needs_fill) {
image.compute_unswizzle_buffer_is_zero = true;
}
// Size of one unswizzled z-slice in the output buffer (bytes).
// bytes_per_block was removed here at one point but caused graphics corruption which makes sense as this is processing DXT1-7 textures and without it I'll be initilizing a buffer that is far far smaller than the actual texture
// I can look more into this later if at some point I want this to work with non-DXT textures
const VkDeviceSize output_slice_bytes =
static_cast<VkDeviceSize>(blocks_x) * blocks_y * bytes_per_block;
const VkDeviceSize fill_size = output_slice_bytes * z_count;
scheduler.Record([out_buffer, dst_image, aspect, z_dst, z_count,
buffer_capacity, needs_fill,
image_width, image_height](vk::CommandBuffer cmdbuf) {
fill_size, image_width, image_height](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
}
if (needs_fill) {
// Zero the output buffer region that CopyBufferToImage will read.
cmdbuf.FillBuffer(out_buffer, 0, buffer_capacity, 0u);
// Zero the output buffer region that CopyBufferToImage will read.
cmdbuf.FillBuffer(out_buffer, 0, fill_size, 0u);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = 0,
.size = buffer_capacity,
};
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = 0,
.size = fill_size,
};
cmdbuf.PipelineBarrier(
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, buffer_barrier);
}
cmdbuf.PipelineBarrier(
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, buffer_barrier);
// Copy the zeroed buffer region into the correct Z position of the image.
const VkBufferImageCopy copy{

9
src/video_core/renderer_vulkan/vk_compute_pass.h

@ -150,7 +150,6 @@ public:
std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_src_start, u32 z_image_start, u32 z_count,
std::span<const u8> slice_has_data,
std::span<const VideoCommon::Accelerated::SliceBBox> slice_bounds,
bool image_already_uploaded);
void UnswizzleChunk(
@ -158,12 +157,14 @@ public:
const StagingBufferRef &swizzled,
const VideoCommon::SwizzleParameters &sw,
const BlockLinearSwizzle3DParams &params,
u32 origin_x, u32 origin_y,
u32 extent_x, u32 extent_y,
u32 z_src, u32 z_dst, u32 z_count);
u32 blocks_x, u32 blocks_y,
u32 z_src, u32 z_dst, u32 z_count,
u32 src_buffer_byte_bias);
void UnswizzleZeroChunk(
Image &image,
u32 blocks_x, u32 blocks_y,
u32 bytes_per_block,
u32 z_dst, u32 z_count);
private:

37
src/video_core/renderer_vulkan/vk_texture_cache.cpp

@ -949,13 +949,14 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
}
}
[[nodiscard]] boost::container::small_vector<VkFormat, 8> BuildViewFormats(
const ImageInfo& info, std::span<const VkFormat> base_view_formats) {
[[nodiscard]] boost::container::small_vector<VkFormat, 8> BuildViewFormats(const ImageInfo& info, std::span<const VkFormat> base_view_formats) {
boost::container::small_vector<VkFormat, 8> formats(base_view_formats.begin(),
base_view_formats.end());
if (Settings::values.gpu_unswizzle_enabled.GetValue()) {
if (const auto block_view_format = BlockTexelViewFormat(info.format)) {
formats.push_back(*block_view_format);
if (Settings::values.accelerate_unswizzle.GetValue() == Settings::TexUnswizzleMode::Gpu) {
if (!info.is_sparse) {
if (const auto block_view_format = BlockTexelViewFormat(info.format)) {
formats.push_back(*block_view_format);
}
}
}
return formats;
@ -999,11 +1000,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
}
}
if (Settings::values.gpu_unswizzle_enabled.GetValue()) {
bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
if (Settings::values.accelerate_unswizzle.GetValue() == Settings::TexUnswizzleMode::Gpu) {
generic_2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
generic_3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
@ -1011,6 +1008,13 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
generic_linear_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
if (Settings::values.async_unswizzle_mode.GetValue() == Settings::AsyncUnswizzleMode::Gpu) {
bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
}
bool TextureCacheRuntime::IsUnswizzleStorageFormatSupported(PixelFormat format) const {
@ -2989,17 +2993,24 @@ VkRenderPass Framebuffer::RenderPassVariant(u32 color_clear_mask, bool depth_ste
void TextureCacheRuntime::AccelerateImageUpload(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_src_start, u32 z_image_start) {
u32 z_src_start, u32 z_image_start, u32 z_count,
std::span<const u8> slice_has_data,
bool image_already_uploaded) {
if (IsPixelFormatASTC(image.info.format)) {
return astc_decoder_pass->Assemble(image, map, swizzles);
}
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || (!generic_2d_unswizzle_pass && !generic_3d_unswizzle_pass)) {
/*if (!Settings::values.gpu_unswizzle_enabled.GetValue() || (!generic_2d_unswizzle_pass && !generic_3d_unswizzle_pass)) {
ASSERT(false && "GPU unswizzle is disabled for this texture");
return;
}
}*/
if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) {
return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles,
z_src_start, z_image_start, z_count,
slice_has_data, image_already_uploaded);
}
if (image.info.type == ImageType::e2D && generic_2d_unswizzle_pass) {
return generic_2d_unswizzle_pass->Unswizzle(image, map, swizzles);
}

6
src/video_core/renderer_vulkan/vk_texture_cache.h

@ -101,9 +101,11 @@ public:
Settings::TexUnswizzleMode::Gpu;
}
void AccelerateImageUpload(Image &, const StagingBufferRef &,
void AccelerateImageUpload(Image&, const StagingBufferRef&,
std::span<const VideoCommon::SwizzleParameters>,
u32 z_src_start, u32 z_image_start);
u32 z_src_start, u32 z_image_start, u32 z_count,
std::span<const u8> slice_has_data = {},
bool image_already_uploaded = false);
void InsertUploadMemoryBarrier() {}

78
src/video_core/texture_cache/accelerated_swizzle.h

@ -14,9 +14,6 @@
namespace VideoCommon::Accelerated {
constexpr u32 GOB_SIZE_Y = 8;
constexpr u32 GOB_SIZE = 512;
struct BlockLinearSwizzle2DParams {
alignas(16) std::array<u32, 3> origin;
alignas(16) std::array<s32, 3> destination;
@ -41,85 +38,10 @@ struct BlockLinearSwizzle3DParams {
u32 block_depth_mask;
};
struct SliceBBox {
u32 x0 = 0, y0 = 0, x1 = 0, y1 = 0;
};
[[nodiscard]] BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(
const SwizzleParameters& swizzle, const ImageInfo& info);
[[nodiscard]] BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams(
const SwizzleParameters& swizzle, const ImageInfo& info);
template <typename Callback>
void ForEachZInGroupOverlap(u64 local_start, u64 local_end,
u32 block_size, u32 x_shift, u32 block_height,
u32 block_height_mask, u32 block_depth, u32 block_depth_mask,
u32 bytes_per_block, u32 blocks_x, u32 blocks_y,
Callback&& on_result) {
if (local_start >= local_end) return;
const u32 z_pitch = GOB_SIZE << block_height;
const u32 column_pitch = 1u << x_shift;
const u32 blocks_per_column = 64u / bytes_per_block;
const u32 rows_per_group = 1u << block_height;
const u32 y_per_band = GOB_SIZE_Y << block_height;
const u32 zz_count = 1u << block_depth;
const u64 band_start = local_start / block_size;
const u64 band_end = (local_end - 1) / block_size;
if (band_start != band_end) {
SliceBBox box;
box.x0 = 0;
box.x1 = blocks_x;
box.y0 = static_cast<u32>(band_start) * y_per_band;
box.y1 = (std::min)(static_cast<u32>(band_end + 1) * y_per_band, blocks_y);
for (u32 zz = 0; zz < zz_count; ++zz) on_result(zz, box);
return;
}
const u64 wb_start = local_start - band_start * block_size;
const u64 wb_end = (local_end - 1) - band_start * block_size;
const u32 col_start = static_cast<u32>(wb_start / column_pitch);
const u32 col_end = static_cast<u32>(wb_end / column_pitch);
if (col_start != col_end) {
SliceBBox box;
box.x0 = col_start * blocks_per_column;
box.x1 = (std::min)((col_end + 1) * blocks_per_column, blocks_x);
box.y0 = static_cast<u32>(band_start) * y_per_band;
box.y1 = (std::min)(box.y0 + y_per_band, blocks_y);
for (u32 zz = 0; zz < zz_count; ++zz) on_result(zz, box);
return;
}
const u64 wc_start = wb_start - static_cast<u64>(col_start) * column_pitch;
const u64 wc_end = wb_end - static_cast<u64>(col_start) * column_pitch;
const u32 zz_start = static_cast<u32>(wc_start / z_pitch);
const u32 zz_end = static_cast<u32>(wc_end / z_pitch);
if (zz_start != zz_end) {
SliceBBox box;
box.x0 = col_start * blocks_per_column;
box.x1 = (std::min)((col_start + 1) * blocks_per_column, blocks_x);
box.y0 = static_cast<u32>(band_start) * y_per_band;
box.y1 = (std::min)(box.y0 + y_per_band, blocks_y);
for (u32 zz = zz_start; zz <= zz_end; ++zz) on_result(zz, box);
return;
}
const u64 wz_start = wc_start - static_cast<u64>(zz_start) * z_pitch;
const u64 wz_end = wc_end - static_cast<u64>(zz_start) * z_pitch;
const u32 row_start = static_cast<u32>(wz_start) / GOB_SIZE;
const u32 row_end = static_cast<u32>(wz_end) / GOB_SIZE;
SliceBBox box;
box.x0 = col_start * blocks_per_column;
box.x1 = (std::min)((col_start + 1) * blocks_per_column, blocks_x);
box.y0 = (static_cast<u32>(band_start) * rows_per_group + row_start) * GOB_SIZE_Y;
box.y1 = (std::min)((static_cast<u32>(band_start) * rows_per_group + row_end + 1) * GOB_SIZE_Y, blocks_y);
on_result(zz_start, box);
}
} // namespace VideoCommon::Accelerated

928
src/video_core/texture_cache/texture_cache.h
File diff suppressed because it is too large
View File

83
src/video_core/texture_cache/texture_cache_base.h

@ -10,7 +10,9 @@
#include <deque>
#include <limits>
#include <mutex>
#include <optional>
#include <span>
#include <thread>
#include <type_traits>
// TODO: find out which don't require stable iters
#include <unordered_map>
@ -70,6 +72,19 @@ struct AsyncDecodeContext {
std::atomic_bool complete;
};
struct AsyncCpuUnswizzleChunk {
std::span<const u8> swizzled_batch;
std::vector<u8> linear_batch;
u32 z_src = 0;
u32 z_image = 0;
u32 z_count = 0;
u32 group_z_start = 0;
u32 group_z_count = 0;
std::mutex mutex;
std::atomic_bool complete{false};
std::atomic<u32> subjobs_pending{0};
};
using TextureCacheGPUMap = ankerl::unordered_dense::map<u64, std::vector<ImageId>, Common::IdentityHash<u64>>;
class TextureCacheChannelInfo : public ChannelInfo {
@ -132,26 +147,38 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
using AsyncBuffer = typename P::AsyncBuffer;
using BufferType = typename P::BufferType;
// This struct is too huge! It makes me uncomfortable
struct PendingUnswizzle {
ImageId image_id;
VideoCommon::ImageInfo info;
size_t current_offset = 0;
size_t total_size = 0;
AsyncBuffer staging_buffer;
bool owns_staging_buffer = true;
ImageId image_id{};
size_t total_size = 0;
size_t current_offset = 0;
size_t last_submitted_offset = 0;
size_t bytes_per_slice;
bool initialized = false;
bool was_rescaled = false;
bool is_sparse = false;
std::vector<u8> slice_has_data;
size_t staging_base_byte_offset = 0;
size_t bytes_per_slice = 0;
u64 swizzled_slice_size = 0;
std::vector<std::pair<GPUVAddr, size_t>> sparse_segments;
std::vector<u8> slice_has_data;
size_t segment_scan_cursor = 0;
size_t swizzle_group_size = 0;
u32 slices_per_group = 0;
bool is_incremental = false;
size_t staging_base_byte_offset = 0;
u32 incremental_z_start = 0;
u32 incremental_z_count = 0;
u32 active_z_start = 0;
u32 active_z_end = 0;
std::unique_ptr<AsyncCpuUnswizzleChunk> cpu_chunk;
Extent3D cpu_num_tiles{};
Extent3D cpu_block{};
u32 swizzle_block_depth = 0;
u32 cpu_stride_alignment = 0;
u32 cpu_bytes_per_block = 0;
bool initialized = false;
bool is_sparse = false;
bool is_cpu = false;
bool cpu_job_in_flight = false;
};
struct BlitImages {
@ -438,6 +465,13 @@ private:
void QueueAsyncUnswizzle(Image& image, ImageId image_id);
void TickAsyncUnswizzle();
void TickAsyncUnswizzleGpu(PendingUnswizzle& task, Image& image);
void TickAsyncUnswizzleCpu(PendingUnswizzle& task, Image& image);
void InitializeCpuUnswizzleTask(PendingUnswizzle& task, Image& image);
void StageSwizzledDataCpu(PendingUnswizzle& task, Image& image, size_t current_group_off,
size_t current_group_end);
void DispatchCpuUnswizzleJob(PendingUnswizzle& task, Image& image, u32 z_src, u32 z_count);
void PollAndUploadCpuUnswizzleJob(PendingUnswizzle& task, Image& image);
bool IsUnswizzleStorageFormatSupported(PixelFormat format) {
return runtime.IsUnswizzleStorageFormatSupported(format);
@ -472,9 +506,11 @@ private:
u64 minimum_memory;
u64 expected_memory;
u64 critical_memory;
size_t gpu_unswizzle_maxsize = 0;
size_t swizzle_chunk_size = 0;
u32 swizzle_slices_per_batch = 0;
Settings::AsyncUnswizzleMode async_unswizzle_mode = Settings::AsyncUnswizzleMode::Off;
size_t async_unswizzle_maxsize = 0;
size_t async_unswizzle_chunk_size = 0;
u32 async_unswizzle_slices_per_batch = 0;
struct BufferDownload {
GPUVAddr address;
@ -527,12 +563,25 @@ private:
u64 frame_tick = 0;
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"};
// I kinda don't want ASTC CPU async to flood your threads but eh, lets FAFO
static u32 ComputeTextureDecodeWorkerCount() {
const u32 hw = std::thread::hardware_concurrency();
return (std::max)(1u, hw > 2 ? hw - 1 : hw);
}
const u32 texture_decode_worker_count = ComputeTextureDecodeWorkerCount();
Common::ThreadWorker texture_decode_worker{texture_decode_worker_count, "TextureDecoder"};
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
Common::ThreadPlacement::Efficiency};
std::vector<std::unique_ptr<AsyncDecodeContext>> async_decodes;
std::deque<PendingUnswizzle> unswizzle_queue;
static constexpr size_t UnswizzleSharedStagingCap = 1_GiB;
std::optional<AsyncBuffer> unswizzle_shared_staging;
size_t unswizzle_shared_staging_capacity = 0;
bool unswizzle_shared_staging_pending_gpu_read = false;
// Join caching
boost::container::small_vector<ImageId, 4> join_overlap_ids;
ankerl::unordered_dense::set<ImageId> join_overlaps_found;

133
src/video_core/textures/decoders.cpp

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -12,9 +12,14 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/cpu_features.h"
#include "common/div_ceil.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#if defined(ARCHITECTURE_x86_64)
#include "video_core/textures/decoders_avx2.h"
#include "video_core/textures/decoders_sse2.h"
#endif
namespace Tegra::Texture {
namespace {
@ -58,34 +63,90 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
const u32 block_depth_mask = (1U << block_depth) - 1;
const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth;
for (u32 slice = 0; slice < depth; ++slice) {
const u32 z = slice + origin_z;
const u32 offset_z = (z >> block_depth) * slice_size +
((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 y = line + origin_y;
const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
if constexpr (BYTES_PER_PIXEL == 1 || BYTES_PER_PIXEL == 2 || BYTES_PER_PIXEL == 4 ||
BYTES_PER_PIXEL == 8 || BYTES_PER_PIXEL == 16) {
static constexpr u32 RUN_BYTES = 16;
static constexpr u32 PIXELS_PER_RUN = RUN_BYTES / BYTES_PER_PIXEL;
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
for (u32 slice = 0; slice < depth; ++slice) {
const u32 z = slice + origin_z;
const u32 offset_z = (z >> block_depth) * slice_size +
((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 y = line + origin_y;
const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < width;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
u32 column = 0;
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
for (; column + PIXELS_PER_RUN <= width;
column += PIXELS_PER_RUN, incrpdep<SWIZZLE_X_BITS, RUN_BYTES>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
std::memcpy(dst, src, BYTES_PER_PIXEL);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, RUN_BYTES);
}
for (; column < width;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
}
}
} else {
for (u32 slice = 0; slice < depth; ++slice) {
const u32 z = slice + origin_z;
const u32 offset_z = (z >> block_depth) * slice_size +
((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 y = line + origin_y;
const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < width;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
}
}
}
@ -157,6 +218,32 @@ void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 wid
template <bool TO_LINEAR>
void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
// This is quite ugly and has duplicate code but I want to make sure this is an actual speedup on stuff like SteamDeck
#if defined(ARCHITECTURE_x86_64)
if constexpr (!TO_LINEAR) {
switch (bytes_per_pixel) {
case 1:
case 2:
case 4:
case 8:
case 16:
// This needs more testing but in my benchmarks this is roughly 1.5x faster than master
// A check for AVX-512 could be possible but my CPU doesn't support it so no way to test it
if (Common::g_cpu_caps.avx2) {
UnswizzleGobPermuteAVX2(output, input, bytes_per_pixel, width, height, depth,
block_height, block_depth, stride_alignment);
} else {
// SSE2 comes with x64 by default but incase I'm wrong this is here
// Could maybe check for SSE3, SSSE3, SSE4.1 and SSE4.2 but I think they don't really add anything for this scenario
UnswizzleGobPermuteSSE2(output, input, bytes_per_pixel, width, height, depth,
block_height, block_depth, stride_alignment);
}
return;
default:
break;
}
}
#endif
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \

157
src/video_core/textures/decoders_avx2.cpp

@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <span>
#include <immintrin.h> // AVX2
#include "common/assert.h"
#include "common/div_ceil.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/decoders_avx2.h"
namespace Tegra::Texture {
namespace {
template <u32 mask>
constexpr u32 Pdep(u32 value) {
u32 result = 0;
u32 m = mask;
for (u32 bit = 1; m; bit += bit) {
if (value & bit) {
result |= m & (~m + 1);
}
m &= m - 1;
}
return result;
}
template <u32 mask, u32 incr_amount>
void IncrPdep(u32& value) {
static constexpr u32 swizzled_incr = Pdep<mask>(incr_amount);
value = ((value | ~mask) + swizzled_incr) & mask;
}
template <u32 BYTES_PER_PIXEL>
__attribute__((target("avx2"))) void UnswizzleGobPermuteAVX2Impl(std::span<u8> output,
std::span<const u8> input,
u32 width, u32 height, u32 depth,
u32 block_height,
u32 block_depth, u32 stride) {
static_assert(32 % BYTES_PER_PIXEL == 0, "BYTES_PER_PIXEL must evenly divide 32");
static constexpr u32 RUN_BYTES = 32;
static constexpr u32 PIXELS_PER_RUN = RUN_BYTES / BYTES_PER_PIXEL;
const u32 pitch = width * BYTES_PER_PIXEL;
const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
const u32 slice_size =
Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 block_height_mask = (1U << block_height) - 1;
const u32 block_depth_mask = (1U << block_depth) - 1;
const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth;
for (u32 slice = 0; slice < depth; ++slice) {
const u32 offset_z = (slice >> block_depth) * slice_size +
((slice & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 swizzled_y = Pdep<SWIZZLE_Y_BITS>(line);
const u32 block_y = line >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = 0;
u32 column = 0;
for (; column + PIXELS_PER_RUN <= width;
column += PIXELS_PER_RUN, IncrPdep<SWIZZLE_X_BITS, RUN_BYTES>(swizzled_x)) {
const u32 x = column * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[unswizzled_offset];
const u8* const src = &input[swizzled_offset];
const __m128i lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src));
const __m128i hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src + 32));
__m256i val = _mm256_castsi128_si256(lo);
val = _mm256_inserti128_si256(val, hi, 1);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dst), val);
}
if constexpr (BYTES_PER_PIXEL <= 16) {
constexpr u32 PIXELS_PER_16 = 16 / BYTES_PER_PIXEL;
if (column + PIXELS_PER_16 <= width) {
const u32 x = column * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[unswizzled_offset];
const u8* const src = &input[swizzled_offset];
const __m128i val = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src));
_mm_storeu_si128(reinterpret_cast<__m128i*>(dst), val);
column += PIXELS_PER_16;
IncrPdep<SWIZZLE_X_BITS, 16>(swizzled_x);
}
}
for (; column < width;
++column, IncrPdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = column * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[unswizzled_offset];
const u8* const src = &input[swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
}
}
}
} // Anonymous namespace
void UnswizzleGobPermuteAVX2(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride) {
switch (bytes_per_pixel) {
case 1:
return UnswizzleGobPermuteAVX2Impl<1>(output, input, width, height, depth, block_height,
block_depth, stride);
case 2:
return UnswizzleGobPermuteAVX2Impl<2>(output, input, width, height, depth, block_height,
block_depth, stride);
case 4:
return UnswizzleGobPermuteAVX2Impl<4>(output, input, width, height, depth, block_height,
block_depth, stride);
case 8:
return UnswizzleGobPermuteAVX2Impl<8>(output, input, width, height, depth, block_height,
block_depth, stride);
case 16:
return UnswizzleGobPermuteAVX2Impl<16>(output, input, width, height, depth, block_height,
block_depth, stride);
default:
UNREACHABLE();
}
}
} // namespace Tegra::Texture

16
src/video_core/textures/decoders_avx2.h

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include "common/common_types.h"
namespace Tegra::Texture {
void UnswizzleGobPermuteAVX2(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride);
} // namespace Tegra::Texture

131
src/video_core/textures/decoders_sse2.cpp

@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <span>
#include <emmintrin.h> // SSE2
#include "common/assert.h"
#include "common/div_ceil.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/decoders_sse2.h"
namespace Tegra::Texture {
namespace {
template <u32 mask>
constexpr u32 Pdep(u32 value) {
u32 result = 0;
u32 m = mask;
for (u32 bit = 1; m; bit += bit) {
if (value & bit) {
result |= m & (~m + 1);
}
m &= m - 1;
}
return result;
}
template <u32 mask, u32 incr_amount>
void IncrPdep(u32& value) {
static constexpr u32 swizzled_incr = Pdep<mask>(incr_amount);
value = ((value | ~mask) + swizzled_incr) & mask;
}
template <u32 BYTES_PER_PIXEL>
__attribute__((target("sse2"))) void UnswizzleGobPermuteSSE2Impl(std::span<u8> output,
std::span<const u8> input,
u32 width, u32 height, u32 depth,
u32 block_height,
u32 block_depth, u32 stride) {
static_assert(16 % BYTES_PER_PIXEL == 0, "BYTES_PER_PIXEL must evenly divide 16");
static constexpr u32 RUN_BYTES = 16;
static constexpr u32 PIXELS_PER_RUN = RUN_BYTES / BYTES_PER_PIXEL;
const u32 pitch = width * BYTES_PER_PIXEL;
const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
const u32 slice_size =
Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 block_height_mask = (1U << block_height) - 1;
const u32 block_depth_mask = (1U << block_depth) - 1;
const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth;
for (u32 slice = 0; slice < depth; ++slice) {
const u32 offset_z = (slice >> block_depth) * slice_size +
((slice & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 swizzled_y = Pdep<SWIZZLE_Y_BITS>(line);
const u32 block_y = line >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = 0;
u32 column = 0;
for (; column + PIXELS_PER_RUN <= width;
column += PIXELS_PER_RUN, IncrPdep<SWIZZLE_X_BITS, RUN_BYTES>(swizzled_x)) {
const u32 x = column * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[unswizzled_offset];
const u8* const src = &input[swizzled_offset];
const __m128i val = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src));
_mm_storeu_si128(reinterpret_cast<__m128i*>(dst), val);
}
for (; column < width;
++column, IncrPdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = column * BYTES_PER_PIXEL;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
u8* const dst = &output[unswizzled_offset];
const u8* const src = &input[swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
}
}
}
}
} // Anonymous namespace
void UnswizzleGobPermuteSSE2(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride) {
switch (bytes_per_pixel) {
case 1:
return UnswizzleGobPermuteSSE2Impl<1>(output, input, width, height, depth, block_height,
block_depth, stride);
case 2:
return UnswizzleGobPermuteSSE2Impl<2>(output, input, width, height, depth, block_height,
block_depth, stride);
case 4:
return UnswizzleGobPermuteSSE2Impl<4>(output, input, width, height, depth, block_height,
block_depth, stride);
case 8:
return UnswizzleGobPermuteSSE2Impl<8>(output, input, width, height, depth, block_height,
block_depth, stride);
case 16:
return UnswizzleGobPermuteSSE2Impl<16>(output, input, width, height, depth, block_height,
block_depth, stride);
default:
UNREACHABLE();
}
}
} // namespace Tegra::Texture

16
src/video_core/textures/decoders_sse2.h

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include "common/common_types.h"
namespace Tegra::Texture {
void UnswizzleGobPermuteSSE2(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride);
} // namespace Tegra::Texture
Loading…
Cancel
Save