aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/native.cpp11
-rw-r--r--src/common/android/id_cache.cpp3
-rw-r--r--src/common/android/id_cache.h2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp23
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp42
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp15
-rw-r--r--src/shader_recompiler/profile.h7
-rw-r--r--src/video_core/host1x/codecs/decoder.cpp66
-rw-r--r--src/video_core/host1x/codecs/decoder.h8
-rw-r--r--src/video_core/host1x/codecs/h264.cpp4
-rw-r--r--src/video_core/host1x/codecs/vp8.cpp1
-rw-r--r--src/video_core/host1x/codecs/vp9.cpp1
-rw-r--r--src/video_core/host1x/ffmpeg.cpp169
-rw-r--r--src/video_core/host1x/ffmpeg.h29
-rw-r--r--src/video_core/host1x/nvdec.cpp1
-rw-r--r--src/video_core/host1x/vic.cpp9
-rw-r--r--src/video_core/host1x/vic.h1
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp25
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp53
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h17
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp17
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp18
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h3
32 files changed, 490 insertions, 92 deletions
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 <frontend_common/content_manager.h>
#include <jni.h>
+extern "C" {
+#include <libavcodec/jni.h>
+}
+
#include "common/android/multiplayer/multiplayer.h"
#include "common/android/android_common.h"
#include "common/android/id_cache.h"
@@ -679,6 +683,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,
[[maybe_unused]] jobject surf) {
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<void **>(&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<u32>(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<u32>(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::Decode() {
- if (!initialized) {
+void Decoder::SetFrameDimensions(s32 width, s32 height) {
+ if (width <= 0 || height <= 0) {
+ frame_dimensions.reset();
return;
}
+ frame_dimensions = FFmpeg::FrameDimensions{width, height};
+}
- const auto packet_data = ComposeFrame();
- // Send assembled bitstream to decoder.
- if (!decode_api.SendPacket(packet_data)) {
+void Decoder::Decode() {
+ if (!initialized) {
return;
}
- // Only receive/store visible frames.
- if (vp9_hidden_frame) {
- return;
+ const auto packet_data = ComposeFrame();
+ 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 {
+ std::tie(offsets.luma, std::ignore) = GetProgressiveOffsets();
}
- // Receive output frames from decoder.
- auto frame = decode_api.ReceiveFrame();
-
- if (!frame) {
+ // Send assembled bitstream to decoder.
+ if (!decode_api.SendPacket(packet_data, offsets, GetFrameDimensions())) {
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);
- }
-
+ auto push = [&](u64 luma, std::shared_ptr<FFmpeg::Frame> frame) {
if (UsingDecodeOrder()) {
- host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
- host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
+ host1x.frame_queue.PushDecodeOrder(id, luma, std::move(frame));
} else {
- host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
- host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
+ host1x.frame_queue.PushPresentOrder(id, luma, std::move(frame));
}
- } else {
- auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
+ };
- if (!frame.get()) {
- LOG_ERROR(HW_GPU, "Nvdec {} failed to decode progressive frame for luma {:#X}", id,
- luma_offset);
+ while (auto result = decode_api.ReceiveFrame()) {
+ auto& [frame, frame_offsets] = *result;
+ if (!frame) {
+ continue;
}
-
- if (UsingDecodeOrder()) {
- host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
+ 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 {
- host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
+ 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 <mutex>
#include <optional>
#include <string_view>
+#include <tuple>
#include <ankerl/unordered_dense.h>
#include <queue>
@@ -46,12 +47,19 @@ protected:
virtual std::tuple<u64, u64, u64, u64> GetInterlacedOffsets() = 0;
virtual bool IsInterlaced() = 0;
+ void SetFrameDimensions(s32 width, s32 height);
+
+ std::optional<FFmpeg::FrameDimensions> 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<FFmpeg::FrameDimensions> 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<const u8> H264::ComposeFrame() {
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context, sizeof(H264DecoderContext));
+ const auto& params = current_context.h264_parameter_set;
+ SetFrameDimensions(static_cast<s32>(params.pic_width_in_mbs) * 16,
+ static_cast<s32>(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<u64, u64, u64, u64> VP8::GetInterlacedOffsets() {
std::span<const u8> VP8::ComposeFrame() {
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_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<const u8> 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 <cstring>
+#include <string_view>
+#include <vector>
+
#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<const u8> 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<u8> ExtractH264ParameterSetExtradata(std::span<const u8> packet) {
+ std::vector<u8> 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<const u8> 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<const u8> extradata) {
+ if (!extradata.empty()) {
+ av_freep(&m_codec_context->extradata);
+ m_codec_context->extradata = static_cast<u8*>(
+ 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<int>(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<const u8> packet_data) {
+bool DecodeApi::SendPacket(std::span<const u8> packet_data, const FrameOffsets& offsets,
+ std::optional<FrameDimensions> dimensions) {
+ if (!m_opened) {
+ std::vector<u8> 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<Frame> DecodeApi::ReceiveFrame() {
- // Receive raw frame from decoder.
- return m_decoder_context->ReceiveFrame();
+std::optional<DecodeApi::DecodedFrame> 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<const u8> extradata = {});
bool SendPacket(const Packet& packet);
std::shared_ptr<Frame> 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<const u8> packet_data);
- std::shared_ptr<Frame> ReceiveFrame();
+ bool SendPacket(std::span<const u8> packet_data, const FrameOffsets& offsets,
+ std::optional<FrameDimensions> dimensions = std::nullopt);
+
+ struct DecodedFrame {
+ std::shared_ptr<Frame> frame;
+ FrameOffsets offsets;
+ };
+ std::optional<DecodedFrame> ReceiveFrame();
private:
std::optional<FFmpeg::Decoder> m_decoder;
std::optional<FFmpeg::DecoderContext> m_decoder_context;
std::optional<FFmpeg::HardwareContext> m_hardware_context;
+ bool m_opened{};
+ bool m_defer_android_mediacodec_open{};
+ bool m_needs_h264_extradata{};
+ s64 m_next_pts{};
+ std::queue<FrameOffsets> 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<u32>(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<ComputePipeline> PipelineCache::CreateComputePipeline(
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<ComputePipeline>(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<u64>(blocks_y) *
static_cast<u64>(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<Shader::TextureType> 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<VkBool32>(!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<VkBool32>(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<ImageView*, NUM_RT> 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<vk::ImageView, Shader::NUM_TEXTURE_TYPES> unsigneds;
};
- [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask);
+ [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask,
+ std::optional<Shader::TextureType> texture_type = std::nullopt);
const Device* device = nullptr;
const SlotVector<Image>* 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<bool>(sampler_nearest);
}
+ [[nodiscard]] VkSampler HandleWithoutDepthComparison() const noexcept {
+ return *sampler_noncompare;
+ }
+
+ [[nodiscard]] bool HasDepthComparison() const noexcept {
+ return static_cast<bool>(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<VkFormat, VkFormatProperties> 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<VkFormat, VkFormatProperties> 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<VkExtensionProperties> 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<VkExtensionProperties> EnumerateDeviceExtensionProperties() const;
std::vector<VkQueueFamilyProperties> GetQueueFamilyProperties() const;