From 372bdfccbc06aead40fc4d14620df6c292728656 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 9 Jul 2026 06:55:54 +0200 Subject: [PATCH] [vulkan, android] Mediacodec implementation + Vulkan fixes (#4191) This is the first step into the conversion on full GPU video decoding for Android; currently due to the VIC structure, I couldn't set it on surface mode to prevent the latency when sending video decoded (which is processed by GPU now), because currently we convert YUV420 into the Nvidia's format each frame constantly on CPU, aside the requirements from other extensions to work + VIC rewrite, which is a work currently not planned to happen on this PR; the actual configuration for video decoding is ByteBuffer, using GPU to decode NVDEC data (all codecs supported, h264, vp8 and vp9) and send it to CPU for display purposes, which is more faster than relying purely on CPU for any drawing task, saving devices resources/ heating, the downside on this will be the slight latency when a new frame is displayed, which is gonna be a black frame for less than a second, nothing major to harm the experience rather than actually trying our best to take advantage on hardware accelerated. Aside this, I also added a bunch of minor Vulkan fixes to grant drivers less thinkering when receiving spir-v instructions, meaning this has new bans for extensions on QCOM (following reported issues on other projects working around Adreno driver behavior), this more than providing performance aims to enhance the stability on the driver, performance it's gonna likely to be hit based on the UBO's (StorageBufferAccess) operations and SSBO (uniformStorageBufferAccess), there was an already existing path for the emulation which forces to wide them into 32bit packed operations. I also included some smaller changes/ bugs + VUID's fixes from earlier changes on my work. Special Thanks: -> Mr. Smoly Gidolard (@gidoly) Sources: 1.- https://github.com/microsoft/DirectXShaderCompiler/issues/2842 2.- https://github.com/mlc-ai/web-llm/issues/836 3.- https://github.com/ggml-org/llama.cpp/issues/5186 4.- https://github.com/encounter/aurora/pull/202 Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4191 --- externals/ffmpeg/CMakeLists.txt | 5 + src/android/app/src/main/jni/CMakeLists.txt | 2 +- src/android/app/src/main/jni/native.cpp | 11 ++ src/common/android/id_cache.cpp | 3 + src/common/android/id_cache.h | 2 +- .../backend/spirv/emit_spirv.cpp | 2 +- .../backend/spirv/emit_spirv_memory.cpp | 23 ++- .../spirv/emit_spirv_shared_memory.cpp | 3 + .../backend/spirv/emit_spirv_warp.cpp | 42 ++++- .../backend/spirv/spirv_emit_context.cpp | 15 +- src/shader_recompiler/profile.h | 7 + src/video_core/host1x/codecs/decoder.cpp | 78 ++++---- src/video_core/host1x/codecs/decoder.h | 8 + src/video_core/host1x/codecs/h264.cpp | 4 + src/video_core/host1x/codecs/vp8.cpp | 1 + src/video_core/host1x/codecs/vp9.cpp | 1 + src/video_core/host1x/ffmpeg.cpp | 169 ++++++++++++++++-- src/video_core/host1x/ffmpeg.h | 29 ++- src/video_core/host1x/nvdec.cpp | 1 + src/video_core/host1x/vic.cpp | 9 +- src/video_core/host1x/vic.h | 1 + .../renderer_vulkan/pipeline_helper.h | 4 + .../renderer_vulkan/vk_compute_pass.cpp | 6 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 25 ++- .../renderer_vulkan/vk_compute_pipeline.h | 3 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 18 +- .../renderer_vulkan/vk_query_cache.cpp | 5 +- .../renderer_vulkan/vk_texture_cache.cpp | 53 ++++-- .../renderer_vulkan/vk_texture_cache.h | 17 +- .../vulkan_common/vulkan_device.cpp | 17 +- src/video_core/vulkan_common/vulkan_device.h | 14 ++ .../vulkan_common/vulkan_wrapper.cpp | 18 ++ src/video_core/vulkan_common/vulkan_wrapper.h | 3 + 33 files changed, 501 insertions(+), 98 deletions(-) diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 7908e0a619..0baff02c84 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -59,6 +59,11 @@ endif() if (PLATFORM_PS4 OR PLATFORM_MANAGARM) # Doesn't support VA-API, don't go thru the embarrassment of trying to enable it list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) +elseif (ANDROID) + list(APPEND FFmpeg_HWACCEL_FLAGS + --enable-mediacodec + --enable-jni + ) elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBVA libva) diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index b6fe2d7722..7013691765 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -27,7 +27,7 @@ if (ARCHITECTURE_arm64) target_link_libraries(yuzu-android PRIVATE adrenotools) endif() -target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt) +target_link_libraries(yuzu-android PRIVATE ${FFmpeg_LIBRARIES} OpenSSL::SSL cpp-jwt::cpp-jwt) if (ENABLE_UPDATE_CHECKER) target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER) endif() diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f40177ed03..41ba1b2019 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -36,6 +36,10 @@ #include #include +extern "C" { +#include +} + #include "common/android/multiplayer/multiplayer.h" #include "common/android/android_common.h" #include "common/android/id_cache.h" @@ -678,6 +682,13 @@ const char* fallback_cpu_detection() { } // namespace +extern "C" { +jint InitFFmpegOnLoad(JavaVM* vm) { + av_jni_set_java_vm(vm, nullptr); + return 0; +} +} + extern "C" { void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance, diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 76af1e0fb2..0fb358f6e4 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -427,8 +427,11 @@ namespace Common::Android { extern "C" { #endif + jint InitFFmpegOnLoad(JavaVM *vm); + jint JNI_OnLoad(JavaVM *vm, void *reserved) { s_java_vm = vm; + InitFFmpegOnLoad(vm); JNIEnv *env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 6b48f471b7..6e47acb746 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -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 #pragma once diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index f73fb233cb..7ab2bd4adf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -435,7 +435,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles) && - profile.support_vote) { + profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); if (!profile.warp_size_potentially_larger_than_guest) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp index bdcbccfde9..4a5e7ef067 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -156,7 +159,8 @@ void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) { } Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U8, ctx.storage_types.U8, sizeof(u8), &StorageDefinitions::U8)); @@ -167,7 +171,8 @@ Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S8, ctx.storage_types.S8, sizeof(s8), &StorageDefinitions::S8)); @@ -178,7 +183,8 @@ Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U16, ctx.storage_types.U16, sizeof(u16), &StorageDefinitions::U16)); @@ -189,7 +195,8 @@ Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Valu } Id EmitLoadStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S16, ctx.storage_types.S16, sizeof(s16), &StorageDefinitions::S16)); @@ -227,7 +234,7 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8) { + if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8, sizeof(u8), &StorageDefinitions::U8); } else { @@ -237,7 +244,7 @@ void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8) { + if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8, sizeof(s8), &StorageDefinitions::S8); } else { @@ -247,7 +254,7 @@ void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16) { + if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16, sizeof(u16), &StorageDefinitions::U16); } else { @@ -257,7 +264,7 @@ void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::V void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16) { + if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16, sizeof(s16), &StorageDefinitions::S16); } else { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index df05dad74a..c3208b955a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 77ff8c5731..242426ec08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } +bool StageSupportsSubgroups(EmitContext& ctx) { + return ctx.profile.SupportsSubgroupStage(ctx.stage); +} + Id GetThreadId(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -68,6 +78,9 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask } Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { + if (!StageSupportsSubgroups(ctx)) { + return value; + } return ctx.OpSelect( ctx.U32[1], in_range, ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); @@ -89,6 +102,9 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -102,6 +118,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -115,6 +134,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.true_value; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -129,6 +151,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); + } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -137,22 +162,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -222,7 +262,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + Id mask{GetThreadId(ctx)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b1bed263a6..17ce4d744f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1231,13 +1231,15 @@ void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) { const IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types : IR::Type::U32}; - if (profile.support_int8 && True(used_types & IR::Type::U8)) { + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit && + True(used_types & IR::Type::U8)) { DefineSsbos(*this, storage_types.U8, &StorageDefinitions::U8, info, binding, U8, sizeof(u8)); DefineSsbos(*this, storage_types.S8, &StorageDefinitions::S8, info, binding, S8, sizeof(u8)); } - if (profile.support_int16 && True(used_types & IR::Type::U16)) { + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit && + True(used_types & IR::Type::U16)) { DefineSsbos(*this, storage_types.U16, &StorageDefinitions::U16, info, binding, U16, sizeof(u16)); DefineSsbos(*this, storage_types.S16, &StorageDefinitions::S16, info, binding, S16, @@ -1447,7 +1449,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask) { + if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1461,9 +1463,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) { + if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) && + profile.SupportsSubgroupStage(stage)) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index e95778a0de..0197a5f1d7 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,6 +10,8 @@ namespace Shader { +enum class Stage : u32; + struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; @@ -32,6 +34,7 @@ struct Profile { bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool support_vote{}; + u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; @@ -95,6 +98,10 @@ struct Profile { u64 min_ssbo_alignment{}; u32 max_user_clip_distances{}; + + bool SupportsSubgroupStage(Stage stage) const { + return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; + } }; } // namespace Shader diff --git a/src/video_core/host1x/codecs/decoder.cpp b/src/video_core/host1x/codecs/decoder.cpp index c75059db6f..1d6d3e8af6 100644 --- a/src/video_core/host1x/codecs/decoder.cpp +++ b/src/video_core/host1x/codecs/decoder.cpp @@ -20,58 +20,54 @@ Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::Nv Decoder::~Decoder() = default; +void Decoder::SetFrameDimensions(s32 width, s32 height) { + if (width <= 0 || height <= 0) { + frame_dimensions.reset(); + return; + } + frame_dimensions = FFmpeg::FrameDimensions{width, height}; +} + void Decoder::Decode() { if (!initialized) { return; } const auto packet_data = ComposeFrame(); - // Send assembled bitstream to decoder. - if (!decode_api.SendPacket(packet_data)) { - return; - } - - // Only receive/store visible frames. - if (vp9_hidden_frame) { - return; - } - - // Receive output frames from decoder. - auto frame = decode_api.ReceiveFrame(); - - if (!frame) { - return; - } - - if (IsInterlaced()) { - auto [luma_top, luma_bottom, chroma_top, chroma_bottom] = GetInterlacedOffsets(); - auto frame_copy = frame; - - if (!frame.get()) { - LOG_ERROR(HW_GPU, - "Nvdec {} failed to decode interlaced frame for top {:#X} bottom 0x{:X}", id, - luma_top, luma_bottom); - } - - if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy)); - } else { - host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy)); - } + FFmpeg::FrameOffsets offsets{}; + offsets.hidden = vp9_hidden_frame; + offsets.interlaced = IsInterlaced(); + if (offsets.interlaced) { + std::tie(offsets.luma, offsets.luma_bottom, std::ignore, std::ignore) = + GetInterlacedOffsets(); } else { - auto [luma_offset, chroma_offset] = GetProgressiveOffsets(); + std::tie(offsets.luma, std::ignore) = GetProgressiveOffsets(); + } - if (!frame.get()) { - LOG_ERROR(HW_GPU, "Nvdec {} failed to decode progressive frame for luma {:#X}", id, - luma_offset); - } + // Send assembled bitstream to decoder. + if (!decode_api.SendPacket(packet_data, offsets, GetFrameDimensions())) { + return; + } + auto push = [&](u64 luma, std::shared_ptr frame) { if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushDecodeOrder(id, luma, std::move(frame)); } else { - host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushPresentOrder(id, luma, std::move(frame)); + } + }; + + while (auto result = decode_api.ReceiveFrame()) { + auto& [frame, frame_offsets] = *result; + if (!frame) { + continue; + } + if (frame_offsets.interlaced) { + auto frame_copy = frame; + push(frame_offsets.luma, std::move(frame)); + push(frame_offsets.luma_bottom, std::move(frame_copy)); + } else { + push(frame_offsets.luma, std::move(frame)); } } } diff --git a/src/video_core/host1x/codecs/decoder.h b/src/video_core/host1x/codecs/decoder.h index 9fca89aa40..2cc6d8dce6 100644 --- a/src/video_core/host1x/codecs/decoder.h +++ b/src/video_core/host1x/codecs/decoder.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -46,12 +47,19 @@ protected: virtual std::tuple GetInterlacedOffsets() = 0; virtual bool IsInterlaced() = 0; + void SetFrameDimensions(s32 width, s32 height); + + std::optional GetFrameDimensions() const { + return frame_dimensions; + } + FFmpeg::DecodeApi decode_api; Host1x::Host1x& host1x; const Host1x::NvdecCommon::NvdecRegisters& regs; s32 id; bool initialized : 1 = false; bool vp9_hidden_frame : 1 = false; + std::optional frame_dimensions; }; } // namespace Tegra diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp index f439ac3828..e3a6f5d182 100644 --- a/src/video_core/host1x/codecs/h264.cpp +++ b/src/video_core/host1x/codecs/h264.cpp @@ -52,6 +52,10 @@ bool H264::IsInterlaced() { std::span H264::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext)); + const auto& params = current_context.h264_parameter_set; + SetFrameDimensions(static_cast(params.pic_width_in_mbs) * 16, + static_cast(params.frame_height_in_mbs) * 16); + const s64 frame_number = current_context.h264_parameter_set.frame_number.Value(); if (!is_first_frame && frame_number != 0) { frame_scratch.resize_destructive(current_context.stream_len); diff --git a/src/video_core/host1x/codecs/vp8.cpp b/src/video_core/host1x/codecs/vp8.cpp index 00fe6e4499..33d781816a 100644 --- a/src/video_core/host1x/codecs/vp8.cpp +++ b/src/video_core/host1x/codecs/vp8.cpp @@ -35,6 +35,7 @@ std::tuple VP8::GetInterlacedOffsets() { std::span VP8::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(VP8PictureInfo)); + SetFrameDimensions(current_context.frame_width, current_context.frame_height); const bool is_key_frame = current_context.key_frame == 1u; const auto bitstream_size = size_t(current_context.vld_buffer_size); diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp index 1085e08939..dcc6261e5c 100644 --- a/src/video_core/host1x/codecs/vp9.cpp +++ b/src/video_core/host1x/codecs/vp9.cpp @@ -841,6 +841,7 @@ std::span VP9::ComposeFrame() { { Vp9FrameContainer curr_frame = GetCurrentFrame(); current_frame_info = curr_frame.info; + SetFrameDimensions(current_frame_info.frame_size.width, current_frame_info.frame_size.height); bitstream = std::move(curr_frame.bit_stream); } // The uncompressed header routine sets PrevProb parameters needed for the compressed header diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index 88df6695d8..783259ccfc 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -4,6 +4,10 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + #include "common/assert.h" #include "common/logging.h" #include "common/scope_exit.h" @@ -84,6 +88,52 @@ std::string AVError(int errnum) { return errbuf; } +#if defined(__ANDROID__) +size_t FindNalStartCode(std::span data, size_t i) { + const size_t n = data.size(); + if (i + 3 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1) { + return 4; + } + if (i + 2 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + return 3; + } + return 0; +} + +std::vector ExtractH264ParameterSetExtradata(std::span packet) { + std::vector extradata; + const size_t size = packet.size(); + size_t i = 0; + while (i < size) { + const size_t sc = FindNalStartCode(packet, i); + if (sc == 0) { + ++i; + continue; + } + const size_t nal_start = i + sc; + if (nal_start >= size) { + break; + } + const u8 nal_type = packet[nal_start] & 0x1F; + + size_t j = nal_start + 1; + while (j < size && FindNalStartCode(packet, j) == 0) { + ++j; + } + + if (nal_type == 7 || nal_type == 8) { + constexpr u8 start[4] = {0, 0, 0, 1}; + extradata.insert(extradata.end(), start, start + sizeof(start)); + extradata.insert(extradata.end(), packet.begin() + nal_start, packet.begin() + j); + } else if (nal_type == 1 || nal_type == 5) { + break; + } + i = j; + } + return extradata; +} +#endif + } Packet::Packet(std::span data) { @@ -118,7 +168,24 @@ Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) { return AV_CODEC_ID_NONE; } }(); - m_codec = avcodec_find_decoder(av_codec); + +#if defined(__ANDROID__) + if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + const char* mc_name = nullptr; + switch (av_codec) { + case AV_CODEC_ID_H264: mc_name = "h264_mediacodec"; break; + case AV_CODEC_ID_VP8: mc_name = "vp8_mediacodec"; break; + case AV_CODEC_ID_VP9: mc_name = "vp9_mediacodec"; break; + default: break; + } + if (mc_name) { + m_codec = avcodec_find_decoder_by_name(mc_name); + } + } +#endif + if (!m_codec) { + m_codec = avcodec_find_decoder(av_codec); + } } bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const { @@ -214,6 +281,10 @@ DecoderContext::DecoderContext(const Decoder& decoder) : m_decoder{decoder} { av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0); m_codec_context->thread_count = 0; m_codec_context->thread_type &= ~FF_THREAD_FRAME; +#if defined(__ANDROID__) + m_codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; + m_codec_context->flags2 |= AV_CODEC_FLAG2_FAST; +#endif } DecoderContext::~DecoderContext() { @@ -227,15 +298,25 @@ void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context, A m_codec_context->pix_fmt = hw_pix_fmt; } -bool DecoderContext::OpenContext(const Decoder& decoder) { +bool DecoderContext::OpenContext(const Decoder& decoder, std::span extradata) { + if (!extradata.empty()) { + av_freep(&m_codec_context->extradata); + m_codec_context->extradata = static_cast( + av_mallocz(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); + if (!m_codec_context->extradata) { + LOG_ERROR(HW_GPU, "Failed to allocate extradata"); + return false; + } + std::memcpy(m_codec_context->extradata, extradata.data(), extradata.size()); + m_codec_context->extradata_size = static_cast(extradata.size()); + } + if (const int ret = avcodec_open2(m_codec_context, decoder.GetCodec(), nullptr); ret < 0) { LOG_ERROR(HW_GPU, "avcodec_open2 error: {}", AVError(ret)); return false; } - if (!m_codec_context->hw_device_ctx) { - LOG_INFO(HW_GPU, "Using FFmpeg CPU decoder"); - } + LOG_INFO(HW_GPU, "Using decoder {}", decoder.GetCodec()->name); return true; } @@ -281,6 +362,13 @@ void DecodeApi::Reset() { m_hardware_context.reset(); m_decoder_context.reset(); m_decoder.reset(); + m_opened = false; + m_defer_android_mediacodec_open = false; + m_needs_h264_extradata = false; + m_next_pts = 0; + while (!m_pending_offsets.empty()) { + m_pending_offsets.pop(); + } } bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { @@ -288,29 +376,90 @@ bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { m_decoder.emplace(codec); m_decoder_context.emplace(*m_decoder); + bool is_mediacodec = false; +#if defined(__ANDROID__) + const std::string_view decoder_name = m_decoder->GetCodec() ? m_decoder->GetCodec()->name : ""; + is_mediacodec = decoder_name == "h264_mediacodec" || + decoder_name == "vp8_mediacodec" || + decoder_name == "vp9_mediacodec"; +#endif + // Enable GPU decoding if requested. - if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + if (!is_mediacodec && + Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { m_hardware_context.emplace(); m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder); } +#if defined(__ANDROID__) + m_defer_android_mediacodec_open = is_mediacodec; + m_needs_h264_extradata = decoder_name == "h264_mediacodec"; + if (m_defer_android_mediacodec_open) { + return true; + } +#endif + // Open the decoder context. if (!m_decoder_context->OpenContext(*m_decoder)) { this->Reset(); return false; } + m_opened = true; return true; } -bool DecodeApi::SendPacket(std::span packet_data) { +bool DecodeApi::SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions) { + if (!m_opened) { + std::vector extradata; +#if defined(__ANDROID__) + if (m_defer_android_mediacodec_open) { + if (!dimensions) { + return true; + } + + auto* ctx = m_decoder_context->GetCodecContext(); + ctx->width = dimensions->width; + ctx->height = dimensions->height; + ctx->coded_width = dimensions->width; + ctx->coded_height = dimensions->height; + } + + if (m_needs_h264_extradata) { + extradata = ExtractH264ParameterSetExtradata(packet_data); + if (extradata.empty()) { + return true; + } + } +#endif + if (!m_decoder_context->OpenContext(*m_decoder, extradata)) { + this->Reset(); + return false; + } + m_opened = true; + } + if (!offsets.hidden) { + m_pending_offsets.push(offsets); + } FFmpeg::Packet packet(packet_data); + packet.GetPacket()->pts = m_next_pts; + packet.GetPacket()->dts = m_next_pts; + ++m_next_pts; return m_decoder_context->SendPacket(packet); } -std::shared_ptr DecodeApi::ReceiveFrame() { - // Receive raw frame from decoder. - return m_decoder_context->ReceiveFrame(); +std::optional DecodeApi::ReceiveFrame() { + auto frame = m_decoder_context->ReceiveFrame(); + if (!frame) { + return std::nullopt; + } + FrameOffsets offsets{}; + if (!m_pending_offsets.empty()) { + offsets = m_pending_offsets.front(); + m_pending_offsets.pop(); + } + return DecodedFrame{std::move(frame), offsets}; } } diff --git a/src/video_core/host1x/ffmpeg.h b/src/video_core/host1x/ffmpeg.h index fdb6908bb6..00cd3f5d4d 100644 --- a/src/video_core/host1x/ffmpeg.h +++ b/src/video_core/host1x/ffmpeg.h @@ -179,7 +179,7 @@ public: ~DecoderContext(); void InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt); - bool OpenContext(const Decoder& decoder); + bool OpenContext(const Decoder& decoder, std::span extradata = {}); bool SendPacket(const Packet& packet); std::shared_ptr ReceiveFrame(); @@ -198,6 +198,18 @@ private: bool m_decode_order{}; }; +struct FrameOffsets { + bool interlaced{}; + bool hidden{}; + u64 luma{}; + u64 luma_bottom{}; +}; + +struct FrameDimensions { + s32 width{}; + s32 height{}; +}; + class DecodeApi { public: YUZU_NON_COPYABLE(DecodeApi); @@ -213,13 +225,24 @@ public: return m_decoder_context->UsingDecodeOrder(); } - bool SendPacket(std::span packet_data); - std::shared_ptr ReceiveFrame(); + bool SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions = std::nullopt); + + struct DecodedFrame { + std::shared_ptr frame; + FrameOffsets offsets; + }; + std::optional ReceiveFrame(); private: std::optional m_decoder; std::optional m_decoder_context; std::optional m_hardware_context; + bool m_opened{}; + bool m_defer_android_mediacodec_open{}; + bool m_needs_h264_extradata{}; + s64 m_next_pts{}; + std::queue m_pending_offsets; }; } // namespace FFmpeg diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp index f2e5c358d8..5dbc6a417e 100644 --- a/src/video_core/host1x/nvdec.cpp +++ b/src/video_core/host1x/nvdec.cpp @@ -31,6 +31,7 @@ Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt) Nvdec::~Nvdec() { LOG_INFO(HW_GPU, "Destroying nvdec {}", id); + host1x.frame_queue.Close(id); } void Nvdec::ProcessMethod(u32 method, u32 argument) { diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index f7830a3a45..6514508947 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -118,6 +118,7 @@ void Vic::Execute() noexcept { output_surface.resize(output_width * output_height); if (Settings::values.nvdec_emulation.GetValue() != Settings::NvdecEmulation::Off) { + bool decoded_frame = false; for (size_t i = 0; i < config.slot_structs.size(); i++) { if (auto& slot_config = config.slot_structs[i]; slot_config.config.slot_enable) { auto const luma_offset = regs.surfaces[i][SurfaceIndex::Current].luma.Address(); @@ -136,11 +137,17 @@ void Vic::Execute() noexcept { break; } Blend(config, slot_config, config.output_surface_config.out_pixel_format); + decoded_frame = true; } else { - LOG_ERROR(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); + LOG_TRACE(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); } } } + if (decoded_frame) { + has_decoded_frame = true; + } else if (!has_decoded_frame) { + return; + } } else { // Fill the frame with black, as otherwise they can have random data and be very glitchy. std::fill(output_surface.begin(), output_surface.end(), Pixel{}); diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h index 23f3f66c18..18eff3382f 100644 --- a/src/video_core/host1x/vic.h +++ b/src/video_core/host1x/vic.h @@ -628,6 +628,7 @@ private: s32 id; s32 nvdec_id{-1}; + bool has_decoded_frame{}; u32 syncpoint; }; diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 0523a040c8..882372aee2 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -242,6 +242,10 @@ inline void PushImageDescriptors(TextureCache& texture_cache, VideoCore::Surface::IsPixelFormatInteger(image_view.format)) { vk_sampler = sampler.HandleWithNearestFilter(); } + if (desc.is_depth && sampler.HasDepthComparison() && + !image_view.SupportsDepthComparison()) { + vk_sampler = sampler.HandleWithoutDepthComparison(); + } guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); const bool element_rescaled{texture_cache.IsRescaling(image_view)}; is_rescaled |= element_rescaled; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index f361c34f44..edc0283d9a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -746,10 +746,8 @@ void BlockLinearUnswizzle3DPass::Unswizzle( const u32 MAX_BATCH_SLICES = (std::min)(z_count, image.info.size.depth); - if (!image.has_compute_unswizzle_buffer) { - // Allocate exactly what this batch needs - image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); - } + // Allocate or grow to cover this batch's slice count + image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); ASSERT(swizzles.size() == 1); const auto& sw = swizzles[0]; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 5525ce0e95..006bfc0a09 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -22,6 +22,7 @@ #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" +#include "common/logging.h" #include "common/settings.h" namespace Vulkan { @@ -36,10 +37,10 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_, - vk::ShaderModule spv_module_) + vk::ShaderModule spv_module_, u64 shader_hash_) : device{device_}, pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_}, - spv_module(std::move(spv_module_)) { + shader_hash{shader_hash_}, spv_module(std::move(spv_module_)) { if (shader_notify) { shader_notify->MarkShaderBuilding(); } @@ -68,7 +69,7 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + const VkComputePipelineCreateInfo compute_ci{ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = flags, @@ -85,7 +86,20 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk .layout = *pipeline_layout, .basePipelineHandle = 0, .basePipelineIndex = 0, - }, *pipeline_cache); + }; + try { + pipeline = device.GetLogical().CreateComputePipeline(compute_ci, *pipeline_cache); + } catch (const vk::Exception& exception) { + LOG_CRITICAL(Render_Vulkan, "Adreno rejected compute shader {:016X}: {}", shader_hash, + exception.what()); + std::scoped_lock lock{build_mutex}; + is_built = true; + build_condvar.notify_one(); + if (shader_notify) { + shader_notify->MarkShaderComplete(); + } + return; + } // Log compute pipeline creation if (GPU::Logging::IsActive()) { @@ -239,6 +253,9 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty(); scheduler.Record([this, descriptor_data, is_rescaling, rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) { + if (!pipeline) { + return; + } cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); if (!descriptor_set_layout) { return; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 1feeed4840..c9704b7c6c 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -37,7 +37,7 @@ public: Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info, - vk::ShaderModule spv_module); + vk::ShaderModule spv_module, u64 shader_hash); ComputePipeline& operator=(ComputePipeline&&) noexcept = delete; ComputePipeline(ComputePipeline&&) noexcept = delete; @@ -53,6 +53,7 @@ private: vk::PipelineCache& pipeline_cache; GuestDescriptorQueue& guest_descriptor_queue; Shader::Info info; + u64 shader_hash{}; u32 num_descriptor_entries{}; VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2eea66802d..6ca9fe4c71 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -353,6 +353,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; + const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { + return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; + }}; + const u32 supported_subgroup_stages{ + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + Shader::Stage::TessellationControl) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + Shader::Stage::TessellationEval) | + subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | + subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, @@ -382,6 +396,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), + .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), @@ -930,7 +945,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline( Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique(device, scheduler, vulkan_pipeline_cache, descriptor_pool, guest_descriptor_queue, thread_worker, statistics, - &shader_notify, program.info, std::move(spv_module)); + &shader_notify, program.info, std::move(spv_module), + key.unique_hash); } catch (const Shader::Exception& exception) { LOG_ERROR(Render_Vulkan, "{}", exception.what()); diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 8d352f3b73..709f65dc25 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -236,7 +236,8 @@ public: } PauseCounter(); const auto driver_id = device.GetDriverID(); - if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || + driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { pending_sync.clear(); sync_values_stash.clear(); return; @@ -1520,7 +1521,7 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku auto driver_id = impl->device.GetDriverID(); const bool is_gpu_high = Settings::IsGPULevelHigh(); - if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { EndHostConditionalRendering(); return true; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index ac4bc29905..960dafb99d 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1635,9 +1635,6 @@ Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBas Image::~Image() = default; void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { - if (has_compute_unswizzle_buffer) - return; - using VideoCore::Surface::BytesPerBlock; const u32 block_bytes = BytesPerBlock(info.format); // 8 for BC1, 16 for BC6H @@ -1654,7 +1651,12 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { static_cast(blocks_y) * static_cast(blocks_z); - compute_unswizzle_buffer_size = block_count * block_bytes; + const VkDeviceSize required_size = block_count * block_bytes; + if (has_compute_unswizzle_buffer && required_size <= compute_unswizzle_buffer_size) { + return; + } + + compute_unswizzle_buffer_size = required_size; VkBufferCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, @@ -2152,6 +2154,15 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI if (uses_widened_astc_format) { format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } + if (device->ApiVersion() >= VK_API_VERSION_1_3) { + const VkFormatProperties3 properties3 = + device->GetPhysical().GetFormatProperties3(format_info.format); + supports_depth_comparison = + (properties3.optimalTilingFeatures & + VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT) != 0; + } else { + supports_depth_comparison = true; + } const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage; @@ -2290,7 +2301,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (uses_widened_astc_format) { info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } - typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); + typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type); } return *typeless_storage_view; } @@ -2301,7 +2312,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds}; auto& view{views[size_t(texture_type)]}; if (!view) - view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT); + view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT, texture_type); return *view; } return VK_NULL_HANDLE; @@ -2311,13 +2322,28 @@ bool ImageView::IsRescaled() const noexcept { return (*slot_images)[image_id].IsRescaled(); } -vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) { +vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type) { + VkImageViewType view_type = ImageViewType(type); + VkImageSubresourceRange subresource_range = MakeSubresourceRange(aspect_mask, range); + if (texture_type) { + view_type = ImageViewType(*texture_type); + switch (view_type) { + case VK_IMAGE_VIEW_TYPE_1D_ARRAY: + case VK_IMAGE_VIEW_TYPE_2D_ARRAY: + case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: + break; + default: + subresource_range.layerCount = 1; + break; + } + } return device->GetLogical().CreateImageView({ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = image_handle, - .viewType = ImageViewType(type), + .viewType = view_type, .format = vk_format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -2325,7 +2351,7 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_ .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, - .subresourceRange = MakeSubresourceRange(aspect_mask, range), + .subresourceRange = subresource_range, }); } @@ -2376,7 +2402,8 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t min_filter == VK_FILTER_LINEAR || mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; - const auto create_sampler = [&](const f32 anisotropy, bool force_nearest) { + const auto create_sampler = [&](const f32 anisotropy, bool force_nearest, + bool disable_compare = false) { return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = pnext, @@ -2391,7 +2418,8 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t .anisotropyEnable = static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), .maxAnisotropy = force_nearest ? 1.0f : anisotropy, - .compareEnable = tsc.depth_compare_enabled, + .compareEnable = disable_compare ? VK_FALSE + : static_cast(tsc.depth_compare_enabled), .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), @@ -2410,6 +2438,9 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t if (has_linear_filtering) { sampler_nearest = create_sampler(1.0f, true); } + if (tsc.depth_compare_enabled) { + sampler_noncompare = create_sampler(max_anisotropy, false, true); + } } Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 027143fdbc..13f51555a3 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -356,6 +356,10 @@ public: return samples; } + [[nodiscard]] bool SupportsDepthComparison() const noexcept { + return supports_depth_comparison; + } + [[nodiscard]] GPUVAddr GpuAddr() const noexcept { return gpu_addr; } @@ -370,7 +374,8 @@ private: std::array unsigneds; }; - [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); + [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type = std::nullopt); const Device* device = nullptr; const SlotVector* slot_images = nullptr; @@ -388,6 +393,7 @@ private: u32 buffer_size = 0; bool uses_widened_astc_format = false; + bool supports_depth_comparison = false; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; @@ -416,10 +422,19 @@ public: return static_cast(sampler_nearest); } + [[nodiscard]] VkSampler HandleWithoutDepthComparison() const noexcept { + return *sampler_noncompare; + } + + [[nodiscard]] bool HasDepthComparison() const noexcept { + return static_cast(sampler_noncompare); + } + private: vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; vk::Sampler sampler_nearest; + vk::Sampler sampler_noncompare; }; struct TextureCacheParams { diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index ab6395cef7..8bddca0c70 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -300,6 +300,10 @@ ankerl::unordered_dense::map GetFormatProperties(v VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, + VK_FORMAT_EAC_R11_UNORM_BLOCK, + VK_FORMAT_EAC_R11_SNORM_BLOCK, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK, }; ankerl::unordered_dense::map format_properties; for (const auto format : formats) { @@ -502,9 +506,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken border color swizzle."); + RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable."); + RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); @@ -513,6 +523,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_atomic_int64.shaderBufferInt64Atomics = false; features.shader_atomic_int64.shaderSharedInt64Atomics = false; features.features.shaderInt64 = false; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken storage buffer access."); + features.bit8_storage.storageBuffer8BitAccess = false; + features.bit8_storage.uniformAndStorageBuffer8BitAccess = false; + features.bit16_storage.storageBuffer16BitAccess = false; + features.bit16_storage.uniformAndStorageBuffer16BitAccess = false; #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a98717bc3e..db92df70a6 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -399,6 +399,16 @@ FN_MAX_LIMIT_LIST return features.bit16_storage.uniformAndStorageBuffer16BitAccess; } + /// Returns true if the device supports reading 8-bit values from a storage buffer. + bool IsStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.storageBuffer8BitAccess; + } + + /// Returns true if the device supports reading 16-bit values from a storage buffer. + bool IsStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.storageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; @@ -419,6 +429,10 @@ FN_MAX_LIMIT_LIST return properties.subgroup_properties.supportedOperations & feature; } + VkShaderStageFlags GetSubgroupSupportedStages() const { + return properties.subgroup_properties.supportedStages; + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index e5d5e93426..4943705859 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -301,6 +301,7 @@ bool Load(VkInstance instance, InstanceDispatch& dld) noexcept { X(vkDestroyDebugReportCallbackEXT); X(vkDestroySurfaceKHR); X(vkGetPhysicalDeviceFeatures2); + X(vkGetPhysicalDeviceFormatProperties2); X(vkGetPhysicalDeviceProperties2); X(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); X(vkGetPhysicalDeviceSurfaceFormatsKHR); @@ -916,6 +917,23 @@ VkFormatProperties PhysicalDevice::GetFormatProperties(VkFormat format) const no return properties; } +VkFormatProperties3 PhysicalDevice::GetFormatProperties3(VkFormat format) const noexcept { + VkFormatProperties3 properties3{ + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3, + .pNext = nullptr, + .linearTilingFeatures = 0, + .optimalTilingFeatures = 0, + .bufferFeatures = 0, + }; + VkFormatProperties2 properties2{ + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &properties3, + .formatProperties = {}, + }; + dld->vkGetPhysicalDeviceFormatProperties2(physical_device, format, &properties2); + return properties3; +} + std::vector PhysicalDevice::EnumerateDeviceExtensionProperties() const { u32 num; dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, nullptr); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index e50856533e..ef975e2bda 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -180,6 +180,7 @@ struct InstanceDispatch { PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr{}; PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2{}; PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{}; + PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2{}; PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{}; PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2{}; PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{}; @@ -1130,6 +1131,8 @@ public: VkFormatProperties GetFormatProperties(VkFormat) const noexcept; + VkFormatProperties3 GetFormatProperties3(VkFormat) const noexcept; + std::vector EnumerateDeviceExtensionProperties() const; std::vector GetQueueFamilyProperties() const;