Compare commits

...

70 Commits

Author SHA1 Message Date
CamilleLaVey a8c6189d53 [vulkan] Restored Presync + ConditionalRendering workaround for QCOM drivers 2026-07-07 23:26:53 -04:00
CamilleLaVey 940cc8caef [vulkan] Add EAC formats on the query format table 2026-07-07 23:21:41 -04:00
CamilleLaVey c57fdebdf0 [vic] Update logging for codec used 2026-07-07 22:27:19 -04:00
CamilleLaVey 059d902ca3 [android] Fix on missing transfer frames 2026-07-07 20:39:28 -04:00
CamilleLaVey 6df0089950 [vic] Quick change on VP9 offsets + ifdef encoding flags for Android 2026-07-07 20:13:56 -04:00
CamilleLaVey d1f41a91ec [android] Verifying the actual flag required on hardware access 2026-07-07 20:08:10 -04:00
CamilleLaVey a1b7cd51b4 [android] Initial implementation for MediaCodec 2026-07-07 19:01:42 -04:00
CamilleLaVey 6a3f885e46 [vulkan] 2D-Array (StorageView) mistmatch handling 2026-07-07 18:41:45 -04:00
CamilleLaVey 18a9d0762b Revert "[TEST] Return of early compute shader change" 2026-07-07 18:30:31 -04:00
CamilleLaVey eb1060381a [TEST] Return of early compute shader change 2026-07-07 17:21:20 -04:00
CamilleLaVey b270bae970 [TEST] Late catch on accelerated image upload path 2026-07-07 16:02:35 -04:00
CamilleLaVey b54be98731 [TEST] Returned shaderFloat16 to QCOM 2026-07-07 15:49:29 -04:00
CamilleLaVey 0e1e0d5664 Fix build 2026-07-06 21:29:23 -04:00
CamilleLaVey 0b17ea1804 [TEST] Remove fp16 from qcom drivers 2026-07-06 20:47:56 -04:00
CamilleLaVey 06580cc6f9 [vulkan, qcom] Updated list of removed features on QCOM drivers 2026-07-06 19:55:19 -04:00
CamilleLaVey bb63996182 [vulkan] Subgroups gating correction 2026-07-06 15:57:53 -04:00
CamilleLaVey 83e1a676c5 [vulkan, qcom] Removed border color swizzle from QCOM drivers 2026-07-06 03:28:53 -04:00
CamilleLaVey 21fa6b5805 [vulkan] Added checks for 8/16bits on storage buffer access 2026-07-06 03:26:03 -04:00
CamilleLaVey 62e8666eee Revert "[TEST] Debugger GPU unswizzle shaders" 2026-07-06 03:19:32 -04:00
CamilleLaVey ca9ff1642f Revert "[TEST] Force checks on subgroups" 2026-07-06 03:16:54 -04:00
CamilleLaVey ace66343ce [TEST] Force checks on subgroups 2026-07-06 03:07:48 -04:00
CamilleLaVey 4bf8828df1 [TEST] Debugger GPU unswizzle shaders 2026-07-06 02:10:21 -04:00
CamilleLaVey 7307518e44 [vulkan] Re-wiring older ASTC decoding path + new helper function on wider ASTC textures 2026-07-05 23:47:22 -04:00
CamilleLaVey 8890b8eb2a [TEST] Removal of the 7 push constant + fixated value 2026-07-05 23:07:57 -04:00
CamilleLaVey a7d274e5bd [TEST] Adjusting sRGB formula 2026-07-05 20:46:33 -04:00
CamilleLaVey bfdc50ee76 [TEST] Refactored SRGBToLinear function 2026-07-05 07:20:59 -04:00
CamilleLaVey b0ccfb2c2a [TEST] Remove sRGB curve convertion 2026-07-05 07:10:41 -04:00
CamilleLaVey f191d7a0b7 Revert "[TEST] Removing AMD/Nvidia from HDR on ASTC decoded formats" 2026-07-05 06:59:15 -04:00
CamilleLaVey 0a0156a484 [TEST] Removing AMD/Nvidia from HDR on ASTC decoded formats 2026-07-05 06:53:33 -04:00
CamilleLaVey e48a8d3ff4 Revert "[TEST] Test for RefreshParametter actual concurrency" 2026-07-05 06:36:31 -04:00
CamilleLaVey 811ee522e0 Revert "[TEST] Diagnosis for corrupted HDR consumed views" 2026-07-05 06:36:06 -04:00
CamilleLaVey 7cb8ae0c0d [TEST] Diagnosis for corrupted HDR consumed views 2026-07-05 06:31:42 -04:00
CamilleLaVey dbb48c4e4e [TEST] Format swap 2026-07-05 06:24:17 -04:00
CamilleLaVey 8cc658e5be [vulkan] Updated Typeless format structure for ASTC decoding 2026-07-05 05:26:47 -04:00
CamilleLaVey 785461a26c [vulkan] Verify actual format of ASTC before decode 2026-07-05 05:06:16 -04:00
CamilleLaVey 7bf0e49a91 [vulkan] Plumbing ASTC decoding capabilities for HDR 2026-07-05 03:45:32 -04:00
CamilleLaVey 55627c3896 [video_core] Initial implementation on HDR endpoints for ASTC decoding 2026-07-05 02:53:52 -04:00
CamilleLaVey 1987384dec [TEST] Test for RefreshParametter actual concurrency 2026-07-05 01:35:12 -04:00
CamilleLaVey 3b617a6733 [TEST] Add B10G11R11 as target format 2026-07-05 01:31:51 -04:00
CamilleLaVey 963e1f633b [TEST] Add preliminary/ missing formats based on driver reports for Switch 2026-07-05 00:36:26 -04:00
CamilleLaVey 6eec277428 [vulkan] Silence unimplemented format entry 2026-07-05 00:07:41 -04:00
CamilleLaVey dbd59bd119 [vulkan] Override default behavior on rasterizer channel 2026-07-04 23:21:46 -04:00
CamilleLaVey 3dc15a4f96 Smolio Gidolardo 2026-07-04 22:10:02 -04:00
CamilleLaVey 4900fee536 [TEST] Re-test removed features from QCOM 2026-07-04 22:05:28 -04:00
CamilleLaVey 34df1b4a16 [vulkan] Another approach for queries reset 2026-07-04 22:03:22 -04:00
CamilleLaVey f5560deab7 [vulkan] Added tracker for reused uploads 2026-07-04 21:41:00 -04:00
CamilleLaVey 307a32f9a0 [vulkan, qcom] Updated list of features removed for QCOM stock drivers 2026-07-04 20:44:25 -04:00
CamilleLaVey 7bb4991edd [vulkan] Refactored dual blending/ dual source attachments 2026-07-04 19:03:27 -04:00
CamilleLaVey f5720663c2 [vulkan] Adjust blend targets from texture types 2026-07-04 18:55:00 -04:00
CamilleLaVey 53927e175e [vulkan] Adjustments on previous TFB barrier added 2026-07-04 18:26:29 -04:00
CamilleLaVey 20a68e1e9d [vulkan, qcom] Returned custom border color and derivates to QCOM drivers 2026-07-04 17:59:07 -04:00
CamilleLaVey c7668fd43e [vulkan] Adjusted checks for 8 - 16bits UBO's iteration 2026-07-04 16:40:15 -04:00
CamilleLaVey eb7f950322 [spir-v] Workaorund for 1D sampled images 2026-07-04 16:24:22 -04:00
CamilleLaVey 80609ee974 [TEST] Remove border color swizzle from qcom 2026-07-04 16:17:01 -04:00
CamilleLaVey 28630ff109 [vulkan] Sampled linear images fallback to nearest 2026-07-04 13:47:44 -04:00
CamilleLaVey b049f7d273 [texture_cache] Relaxed TIC's lookup for integer type image formats 2026-07-04 12:57:20 -04:00
CamilleLaVey f6ccf2c368 [shader_recompiler] Added texture type functions 2026-07-04 01:31:25 -04:00
CamilleLaVey 27a0010b6e [vulkan] Verify UniformAndStorage capabilities on device 2026-07-04 00:32:36 -04:00
CamilleLaVey b06b9cc7a2 [vulkan] Pixel format handling for formats 2026-07-04 00:16:01 -04:00
CamilleLaVey 943f91cf0b [texture_cache] Added typeless storage form 2026-07-04 00:01:31 -04:00
CamilleLaVey bd8caa5294 [texture_cache] Added b5g6r5 pixel format to depth blit resolves 2026-07-03 23:36:05 -04:00
CamilleLaVey 3ddf349ca1 [buffer_cache] Force offsets to 0 on descriptors and use VK_WHOLE_SIZE instead 2026-07-03 23:01:13 -04:00
CamilleLaVey d707d241fc [host_shaders] Corrections on mismatchs for SINT on depth formats 2026-07-03 22:28:42 -04:00
CamilleLaVey aa5d2cf9d7 [qcom] Removal custom border color from QCOM drivers 2026-07-03 22:10:12 -04:00
CamilleLaVey decf41a3cb Fix build 2026-07-03 21:21:14 -04:00
CamilleLaVey d7dc4f2143 [vulkan] Correction on mislead of BindVertexBuffer entries 2026-07-03 20:58:34 -04:00
CamilleLaVey fbd94e4237 [vulkan] Implementation color write enable + color border swizzle 2026-07-03 20:35:45 -04:00
CamilleLaVey 48cc8f4c8c [vulkan] XFB memory barrier added 2026-07-03 02:29:06 -04:00
CamilleLaVey e1721012d8 [vulkan] Set actual dynamic state handling for topology 2026-07-03 01:49:56 -04:00
CamilleLaVey 5af01bef9f [vulkan] Initial implementation Synchronization2 2026-07-03 00:58:40 -04:00
53 changed files with 1851 additions and 241 deletions
+5
View File
@@ -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)
+1 -1
View File
@@ -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()
+11
View File
@@ -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"
@@ -680,6 +684,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,
+3
View File
@@ -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)
+1 -1
View File
@@ -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
@@ -403,6 +403,9 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
if (info.uses_sampled_1d) {
ctx.AddCapability(spv::Capability::Sampled1D);
}
if (info.uses_image_1d) {
ctx.AddCapability(spv::Capability::Image1D);
}
if (info.uses_sparse_residency) {
ctx.AddCapability(spv::Capability::SparseResidency);
}
@@ -432,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) {
@@ -202,7 +202,8 @@ void EmitGetIndirectBranchVariable(EmitContext&) {
}
Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 &&
ctx.profile.support_uniform_and_storage_buffer_8bit) {
const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset,
ctx.load_const_func_u8)};
return ctx.OpUConvert(ctx.U32[1], load);
@@ -219,7 +220,8 @@ Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of
}
Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 &&
ctx.profile.support_uniform_and_storage_buffer_8bit) {
const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset,
ctx.load_const_func_u8)};
return ctx.OpSConvert(ctx.U32[1], load);
@@ -236,7 +238,8 @@ Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of
}
Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 &&
ctx.profile.support_uniform_and_storage_buffer_16bit) {
const Id load{GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset,
ctx.load_const_func_u16)};
return ctx.OpUConvert(ctx.U32[1], load);
@@ -253,7 +256,8 @@ Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o
}
Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) {
if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 &&
ctx.profile.support_uniform_and_storage_buffer_16bit) {
const Id load{GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset,
ctx.load_const_func_u16)};
return ctx.OpSConvert(ctx.U32[1], load);
@@ -260,6 +260,13 @@ bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) {
return ctx.textures.at(info.descriptor_index).is_multisample;
}
bool IsTextureInteger(EmitContext& ctx, const IR::TextureInstInfo& info) {
if (info.type == TextureType::Buffer) {
return false;
}
return ctx.textures.at(info.descriptor_index).is_integer;
}
Id Decorate(EmitContext& ctx, IR::Inst* inst, Id sample) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
if (info.relaxed_precision != 0) {
@@ -480,11 +487,14 @@ Id EmitBoundImageWrite(EmitContext&) {
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id bias_lc, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
Id color;
if (ctx.stage == Stage::Fragment) {
const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0,
bias_lc, offset);
return Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4],
color = Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, result_type,
Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
} else {
// We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as
@@ -492,26 +502,29 @@ Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value&
// derivatives
const Id lod{ctx.Const(0.0f)};
const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
color = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color;
}
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id lod, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
const ImageOperands operands(ctx, false, true, false, lod, offset);
Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
&EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
#ifdef __ANDROID__
if (Settings::values.fix_bloom_effects.GetValue()) {
if (!is_integer && Settings::values.fix_bloom_effects.GetValue()) {
result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f));
}
#endif
return result;
return is_integer ? ctx.OpBitcast(ctx.F32[4], result) : result;
}
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
@@ -547,30 +560,39 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
const ImageOperands operands(ctx, offset, offset2);
if (ctx.profile.need_gather_subpixel_offset) {
coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords);
}
return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst,
ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component),
operands.MaskOptional(), operands.Span());
const Id color{Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst,
result_type, Texture(ctx, info, index), coords,
ctx.Const(info.gather_component), operands.MaskOptional(),
operands.Span())};
return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color;
}
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2, Id dref) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
const ImageOperands operands(ctx, offset, offset2);
if (ctx.profile.need_gather_subpixel_offset) {
coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords);
}
return Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, ctx, inst,
ctx.F32[4], Texture(ctx, info, index), coords, dref, operands.MaskOptional(),
operands.Span());
const Id color{Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather,
ctx, inst, result_type, Texture(ctx, info, index), coords, dref,
operands.MaskOptional(), operands.Span())};
return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color;
}
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
Id lod, Id ms) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
AddOffsetToCoordinates(ctx, info, coords, offset);
if (info.type == TextureType::Buffer) {
lod = Id{};
@@ -580,8 +602,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
lod = Id{};
}
const ImageOperands operands(lod, ms);
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
const Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
result_type, TextureImage(ctx, info, index), coords,
operands.MaskOptional(), operands.Span())};
return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color;
}
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
@@ -626,14 +650,17 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivatives, const IR::Value& offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const bool is_integer{IsTextureInteger(ctx, info)};
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
const auto operands = info.num_derivatives == 3
? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
ctx.Def(offset), {}, lod_clamp)
: ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
info.num_derivatives, offset, lod_clamp);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color;
}
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
@@ -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);
@@ -30,7 +30,7 @@ enum class Operation {
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
const spv::ImageFormat format{spv::ImageFormat::Unknown};
const Id type{ctx.F32[1]};
const Id type{desc.is_integer ? ctx.U32[1] : ctx.F32[1]};
const bool depth{desc.is_depth};
const bool ms{desc.is_multisample};
switch (desc.type) {
@@ -1126,7 +1126,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
}
IR::Type types{info.used_constant_buffer_types | info.used_indirect_cbuf_types};
if (True(types & IR::Type::U8)) {
if (profile.support_int8) {
if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) {
DefineConstBuffers(*this, info, &UniformDefinitions::U8, binding, U8, 'u', sizeof(u8));
DefineConstBuffers(*this, info, &UniformDefinitions::S8, binding, S8, 's', sizeof(s8));
} else {
@@ -1134,7 +1134,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
}
}
if (True(types & IR::Type::U16)) {
if (profile.support_int16) {
if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) {
DefineConstBuffers(*this, info, &UniformDefinitions::U16, binding, U16, 'u',
sizeof(u16));
DefineConstBuffers(*this, info, &UniformDefinitions::S16, binding, S16, 's',
@@ -1196,10 +1196,18 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) {
IR::Type types{info.used_indirect_cbuf_types};
bool supports_aliasing = profile.support_descriptor_aliasing;
if (supports_aliasing && True(types & IR::Type::U8)) {
load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8);
if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) {
load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8);
} else {
types |= IR::Type::U32;
}
}
if (supports_aliasing && True(types & IR::Type::U16)) {
load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16);
if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) {
load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16);
} else {
types |= IR::Type::U32;
}
}
if (supports_aliasing && True(types & IR::Type::F32)) {
load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32);
@@ -1375,6 +1383,7 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
.image_type = image_type,
.count = desc.count,
.is_multisample = desc.is_multisample,
.is_integer = desc.is_integer,
});
if (profile.supported_spirv >= 0x00010400) {
interfaces.push_back(id);
@@ -1438,7 +1447,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);
@@ -1452,9 +1461,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);
@@ -42,6 +42,7 @@ struct TextureDefinition {
Id image_type;
u32 count;
bool is_multisample;
bool is_integer;
};
struct TextureBufferDefinition {
@@ -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
@@ -43,6 +46,10 @@ union TextureInstInfo {
BitField<25, 2, u32> num_derivatives;
BitField<27, 3, ImageFormat> image_format;
BitField<30, 1, u32> ndv_is_active;
/// Only meaningful for the sampled-texture family (ImageSampleImplicitLod, ImageFetch,
/// ImageGather, etc.); unused/zero for storage-image opcodes, which carry their own
/// is_integer via ImageDescriptor/ImageBufferDescriptor instead.
BitField<31, 1, u32> is_integer;
};
static_assert(sizeof(TextureInstInfo) <= sizeof(u32));
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -569,6 +569,8 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::ImageRead: {
const auto flags{inst.Flags<IR::TextureInstInfo>()};
info.uses_typeless_image_reads |= flags.image_format == ImageFormat::Typeless;
info.uses_image_1d |=
flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D;
info.uses_sparse_residency |=
inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr;
break;
@@ -577,6 +579,8 @@ void VisitUsages(Info& info, IR::Inst& inst) {
const auto flags{inst.Flags<IR::TextureInstInfo>()};
info.uses_typeless_image_writes |= flags.image_format == ImageFormat::Typeless;
info.uses_image_buffers |= flags.type == TextureType::Buffer;
info.uses_image_1d |=
flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D;
break;
}
case IR::Opcode::SubgroupEqMask:
@@ -761,9 +765,13 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::ImageAtomicAnd32:
case IR::Opcode::ImageAtomicOr32:
case IR::Opcode::ImageAtomicXor32:
case IR::Opcode::ImageAtomicExchange32:
case IR::Opcode::ImageAtomicExchange32: {
const auto flags{inst.Flags<IR::TextureInstInfo>()};
info.uses_atomic_image_u32 = true;
info.uses_image_1d |=
flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D;
break;
}
default:
break;
}
+20 -1
View File
@@ -562,6 +562,7 @@ public:
})};
// TODO: Read this from TIC
texture_descriptors[index].is_multisample |= desc.is_multisample;
texture_descriptors[index].is_integer |= desc.is_integer;
return index;
}
@@ -685,6 +686,21 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
program.info.image_descriptors,
};
const u32 sampled_dynamic_cap = DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace));
bool has_last_is_integer{false};
u32 last_cbuf_index{};
u32 last_cbuf_offset{};
bool last_is_integer{false};
const auto is_texture_pixel_format_integer{[&](const ConstBufferAddr& cbuf_addr) {
if (has_last_is_integer && last_cbuf_index == cbuf_addr.index &&
last_cbuf_offset == cbuf_addr.offset) {
return last_is_integer;
}
last_is_integer = IsTexturePixelFormatIntegerCached(env, cbuf_addr);
last_cbuf_index = cbuf_addr.index;
last_cbuf_offset = cbuf_addr.offset;
has_last_is_integer = true;
return last_is_integer;
}};
for (TextureInst& texture_inst : to_replace) {
// TODO: Handle arrays
IR::Inst* const inst{texture_inst.inst};
@@ -749,7 +765,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
}
const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead};
const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite};
const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)};
const bool is_integer{is_texture_pixel_format_integer(cbuf)};
if (flags.type == TextureType::Buffer) {
index = descriptors.Add(ImageBufferDescriptor{
.format = flags.image_format,
@@ -791,10 +807,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
});
} else {
count = std::min(count, sampled_dynamic_cap);
const bool is_integer{is_texture_pixel_format_integer(cbuf)};
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
.is_depth = flags.is_depth != 0,
.is_multisample = is_multisample,
.is_integer = is_integer,
.has_secondary = cbuf.has_secondary,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
@@ -805,6 +823,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.count = count,
.size_shift = size_shift,
});
flags.is_integer.Assign(is_integer ? 1 : 0);
}
break;
}
+9
View File
@@ -10,12 +10,16 @@
namespace Shader {
enum class Stage : u32;
struct Profile {
u32 supported_spirv{0x00010000};
bool unified_descriptor_binding{};
bool support_descriptor_aliasing{};
bool support_int8{};
bool support_uniform_and_storage_buffer_8bit{};
bool support_int16{};
bool support_uniform_and_storage_buffer_16bit{};
bool support_int64{};
bool support_vertex_instance_id{};
bool support_float_controls{};
@@ -30,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{};
@@ -93,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
+1
View File
@@ -209,6 +209,7 @@ struct TextureDescriptor {
TextureType type;
bool is_depth;
bool is_multisample;
bool is_integer;
bool has_secondary;
u32 cbuf_index;
u32 cbuf_offset;
+37 -41
View File
@@ -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<FFmpeg::Frame> 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));
}
}
}
+8
View File
@@ -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
+4
View File
@@ -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);
+1
View File
@@ -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);
+1
View File
@@ -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
+159 -10
View File
@@ -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};
}
}
+26 -3
View File
@@ -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
+1
View File
@@ -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) {
+8 -1
View File
@@ -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{});
+1
View File
@@ -628,6 +628,7 @@ private:
s32 id;
s32 nvdec_id{-1};
bool has_decoded_frame{};
u32 syncpoint;
};
+288 -6
View File
@@ -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
@@ -40,7 +43,11 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf
uvec4 astc_data[];
};
#ifdef VULKAN
layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly restrict image2DArray dest_image;
#else
layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image;
#endif
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
@@ -589,6 +596,163 @@ ivec4 BlueContract(int a, int r, int g, int b) {
return ivec4(a, (r + b) >> 1, (g + b) >> 1, b);
}
bool IsHDRColorEndpointMode(uint cem) {
return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u;
}
// Sign-extends the low nbits of value (a 2's complement field packed into the bottom of an
// otherwise-unsigned integer), per C.2.15's HDR endpoint bitfield unpacking.
int SignExtend(int value, uint nbits) {
int sign_bit = 1 << (nbits - 1u);
return (value ^ sign_bit) - sign_bit;
}
// HDR Endpoint Mode 7 (C.2.15): base RGB + scale factor.
void DecodeHDREndpointMode7(uint v0, uint v1, uint v2, uint v3, out ivec3 e0, out ivec3 e1) {
uint modeval = ((v0 & 0xC0u) >> 6u) | ((v1 & 0x80u) >> 5u) | ((v2 & 0x80u) >> 4u);
uint majcomp;
uint mode;
if ((modeval & 0xCu) != 0xCu) {
majcomp = modeval >> 2u;
mode = modeval & 3u;
} else if (modeval != 0xFu) {
majcomp = modeval & 3u;
mode = 4u;
} else {
majcomp = 0u;
mode = 5u;
}
int red = int(v0 & 0x3Fu);
int green = int(v1 & 0x1Fu);
int blue = int(v2 & 0x1Fu);
int scale = int(v3 & 0x1Fu);
uint x0 = (v1 >> 6u) & 1u;
uint x1 = (v1 >> 5u) & 1u;
uint x2 = (v2 >> 6u) & 1u;
uint x3 = (v2 >> 5u) & 1u;
uint x4 = (v3 >> 7u) & 1u;
uint x5 = (v3 >> 6u) & 1u;
uint x6 = (v3 >> 5u) & 1u;
uint ohm = 1u << mode;
if ((ohm & 0x30u) != 0u) green |= int(x0 << 6u);
if ((ohm & 0x3Au) != 0u) green |= int(x1 << 5u);
if ((ohm & 0x30u) != 0u) blue |= int(x2 << 6u);
if ((ohm & 0x3Au) != 0u) blue |= int(x3 << 5u);
if ((ohm & 0x3Du) != 0u) scale |= int(x6 << 5u);
if ((ohm & 0x2Du) != 0u) scale |= int(x5 << 6u);
if ((ohm & 0x04u) != 0u) scale |= int(x4 << 7u);
if ((ohm & 0x3Bu) != 0u) red |= int(x4 << 6u);
if ((ohm & 0x04u) != 0u) red |= int(x3 << 6u);
if ((ohm & 0x10u) != 0u) red |= int(x5 << 7u);
if ((ohm & 0x0Fu) != 0u) red |= int(x2 << 7u);
if ((ohm & 0x05u) != 0u) red |= int(x1 << 8u);
if ((ohm & 0x0Au) != 0u) red |= int(x0 << 8u);
if ((ohm & 0x05u) != 0u) red |= int(x0 << 9u);
if ((ohm & 0x02u) != 0u) red |= int(x6 << 9u);
if ((ohm & 0x01u) != 0u) red |= int(x3 << 10u);
if ((ohm & 0x02u) != 0u) red |= int(x5 << 10u);
int shamts[6] = int[](1, 1, 2, 3, 4, 5);
int shamt = shamts[mode];
red <<= shamt;
green <<= shamt;
blue <<= shamt;
scale <<= shamt;
if (mode != 5u) {
green = red - green;
blue = red - blue;
}
if (majcomp == 1u) {
int t = red; red = green; green = t;
}
if (majcomp == 2u) {
int t = red; red = blue; blue = t;
}
e1 = ivec3(clamp(red, 0, 0xFFF), clamp(green, 0, 0xFFF), clamp(blue, 0, 0xFFF));
e0 = ivec3(clamp(red - scale, 0, 0xFFF), clamp(green - scale, 0, 0xFFF),
clamp(blue - scale, 0, 0xFFF));
}
// HDR Endpoint Mode 11 (C.2.15): direct RGB pair. Shared by modes 11, 14 and 15, which all
// decode their RGB the same way and only differ in how alpha is filled in.
void DecodeHDREndpointMode11(uint v0, uint v1, uint v2, uint v3, uint v4, uint v5, out ivec3 e0,
out ivec3 e1) {
uint majcomp = ((v4 & 0x80u) >> 7u) | ((v5 & 0x80u) >> 6u);
if (majcomp == 3u) {
e0 = ivec3(int(v0 << 4u), int(v2 << 4u), int((v4 & 0x7Fu) << 5u));
e1 = ivec3(int(v1 << 4u), int(v3 << 4u), int((v5 & 0x7Fu) << 5u));
return;
}
uint mode = ((v1 & 0x80u) >> 7u) | ((v2 & 0x80u) >> 6u) | ((v3 & 0x80u) >> 5u);
int va = int(v0 | ((v1 & 0x40u) << 2u));
int vb0 = int(v2 & 0x3Fu);
int vb1 = int(v3 & 0x3Fu);
int vc = int(v1 & 0x3Fu);
int vd0 = int(v4 & 0x7Fu);
int vd1 = int(v5 & 0x7Fu);
int dbitstab[8] = int[](7, 6, 7, 6, 5, 6, 5, 6);
vd0 = SignExtend(vd0, uint(dbitstab[mode]));
vd1 = SignExtend(vd1, uint(dbitstab[mode]));
uint x0 = (v2 >> 6u) & 1u;
uint x1 = (v3 >> 6u) & 1u;
uint x2 = (v4 >> 6u) & 1u;
uint x3 = (v5 >> 6u) & 1u;
uint x4 = (v4 >> 5u) & 1u;
uint x5 = (v5 >> 5u) & 1u;
uint ohm = 1u << mode;
if ((ohm & 0xA4u) != 0u) va |= int(x0 << 9u);
if ((ohm & 0x08u) != 0u) va |= int(x2 << 9u);
if ((ohm & 0x50u) != 0u) va |= int(x4 << 9u);
if ((ohm & 0x50u) != 0u) va |= int(x5 << 10u);
if ((ohm & 0xA0u) != 0u) va |= int(x1 << 10u);
if ((ohm & 0xC0u) != 0u) va |= int(x2 << 11u);
if ((ohm & 0x04u) != 0u) vc |= int(x1 << 6u);
if ((ohm & 0xE8u) != 0u) vc |= int(x3 << 6u);
if ((ohm & 0x20u) != 0u) vc |= int(x2 << 7u);
if ((ohm & 0x5Bu) != 0u) vb0 |= int(x0 << 6u);
if ((ohm & 0x5Bu) != 0u) vb1 |= int(x1 << 6u);
if ((ohm & 0x12u) != 0u) vb0 |= int(x2 << 7u);
if ((ohm & 0x12u) != 0u) vb1 |= int(x3 << 7u);
int shamt = (int(mode) >> 1) ^ 3;
va <<= shamt;
vb0 <<= shamt;
vb1 <<= shamt;
vc <<= shamt;
vd0 <<= shamt;
vd1 <<= shamt;
int r1 = clamp(va, 0, 0xFFF);
int g1 = clamp(va - vb0, 0, 0xFFF);
int b1 = clamp(va - vb1, 0, 0xFFF);
int r0 = clamp(va - vc, 0, 0xFFF);
int g0 = clamp(va - vb0 - vc - vd0, 0, 0xFFF);
int b0 = clamp(va - vb1 - vc - vd1, 0, 0xFFF);
if (majcomp == 1u) {
int t;
t = r0; r0 = g0; g0 = t;
t = r1; r1 = g1; g1 = t;
} else if (majcomp == 2u) {
int t;
t = r0; r0 = b0; b0 = t;
t = r1; r1 = b1; b1 = t;
}
e0 = ivec3(r0, g0, b0);
e1 = ivec3(r1, g1, b1);
}
void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32],
inout uint colvals_index) {
#define READ_UINT_VALUES(N) \
@@ -715,8 +879,88 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui
}
break;
}
case 2: {
READ_UINT_VALUES(2)
uint y0, y1;
if (V[0].y >= V[0].x) {
y0 = V[0].x << 4u;
y1 = V[0].y << 4u;
} else {
y0 = (V[0].y << 4u) + 8u;
y1 = (V[0].x << 4u) - 8u;
}
ep1 = uvec4(0x780u, y0, y0, y0);
ep2 = uvec4(0x780u, y1, y1, y1);
break;
}
case 3: {
READ_UINT_VALUES(2)
uint y0, d;
if ((V[0].x & 0x80u) != 0u) {
y0 = ((V[0].y & 0xE0u) << 4u) | ((V[0].x & 0x7Fu) << 2u);
d = (V[0].y & 0x1Fu) << 2u;
} else {
y0 = ((V[0].y & 0xF0u) << 4u) | ((V[0].x & 0x7Fu) << 1u);
d = (V[0].y & 0x0Fu) << 1u;
}
const uint y1 = min(y0 + d, 0xFFFu);
ep1 = uvec4(0x780u, y0, y0, y0);
ep2 = uvec4(0x780u, y1, y1, y1);
break;
}
case 7: {
READ_UINT_VALUES(4)
ivec3 e0, e1;
DecodeHDREndpointMode7(V[0].x, V[0].y, V[0].z, V[0].w, e0, e1);
ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z));
ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z));
break;
}
case 11: {
READ_UINT_VALUES(6)
ivec3 e0, e1;
DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1);
ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z));
ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z));
break;
}
case 14: {
READ_UINT_VALUES(8)
ivec3 e0, e1;
DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1);
// Only HDR mode with LDR (8-bit UNORM)-interpreted alpha; left as-is (0-255).
ep1 = uvec4(V[1].z, uint(e0.x), uint(e0.y), uint(e0.z));
ep2 = uvec4(V[1].w, uint(e1.x), uint(e1.y), uint(e1.z));
break;
}
case 15: {
READ_UINT_VALUES(8)
ivec3 e0, e1;
DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1);
const uint mode = ((V[1].z >> 7u) & 1u) | ((V[1].w >> 6u) & 2u);
int a6 = int(V[1].z & 0x7Fu);
int a7 = int(V[1].w & 0x7Fu);
int alpha0, alpha1;
if (mode == 3u) {
alpha0 = a6 << 5;
alpha1 = a7 << 5;
} else {
a6 |= (a7 << int(mode + 1u)) & 0x780;
a7 &= int(0x3Fu >> mode);
a7 ^= int(0x20u >> mode);
a7 -= int(0x20u >> mode);
a6 <<= int(4u - mode);
a7 <<= int(4u - mode);
a7 += a6;
alpha0 = a6;
alpha1 = clamp(a7, 0, 0xFFF);
}
ep1 = uvec4(uint(alpha0), uint(e0.x), uint(e0.y), uint(e0.z));
ep2 = uvec4(uint(alpha1), uint(e1.x), uint(e1.y), uint(e1.z));
break;
}
default: {
// HDR mode, or more likely a bug computing the color_endpoint_mode
// Not a valid CEM at all (all 16 values are now handled above).
ep1 = uvec4(0xFF, 0xFF, 0, 0);
ep2 = uvec4(0xFF, 0xFF, 0, 0);
break;
@@ -1112,13 +1356,51 @@ void DecompressBlock(ivec3 coord) {
if (num_partitions > 1) {
local_partition = Select2DPartition(partition_index, i, j, num_partitions);
}
const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]);
const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]);
const uint local_cem = color_endpoint_mode[local_partition];
const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane);
const vec4 Cf =
vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64);
const vec4 p = (Cf / 65535.0f);
vec4 p;
if (IsHDRColorEndpointMode(local_cem)) {
// Endpoints are raw 12-bit pseudo-logarithmic values; shift left 4 bits to
// become 16-bit before interpolating, per C.2.19.
const uvec4 C0 = endpoints0[local_partition] << 4u;
const uvec4 C1 = endpoints1[local_partition] << 4u;
const uvec4 C = (C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64u;
const uvec4 E = (C & uvec4(0xF800u)) >> 11u;
const uvec4 M = C & uvec4(0x7FFu);
const uvec4 Mt_lo = 3u * M;
const uvec4 Mt_mid = 4u * M - 512u;
const uvec4 Mt_hi = 5u * M - 2048u;
const uvec4 Mt =
mix(Mt_lo, mix(Mt_mid, Mt_hi, greaterThanEqual(M, uvec4(1536u))),
greaterThanEqual(M, uvec4(512u)));
const uvec4 Cf = (E << 10u) + (Mt >> 3u);
// +Inf/NaN clamps to the largest finite FP16 value (0x7BFF).
const uvec4 half_bits = mix(Cf, uvec4(0x7BFFu), greaterThanEqual(Cf, uvec4(0x7C00u)));
p = vec4(unpackHalf2x16(half_bits.x).x, unpackHalf2x16(half_bits.y).x,
unpackHalf2x16(half_bits.z).x, unpackHalf2x16(half_bits.w).x);
// Mode 14 keeps an LDR (8-bit UNORM)-interpreted alpha; component 0 here (A,
// see the ep1/ep2 layout used throughout this file).
if (local_cem == 14u) {
const uint a0 = ReplicateByteTo16(uvec4(endpoints0[local_partition].x)).x;
const uint a1 = ReplicateByteTo16(uvec4(endpoints1[local_partition].x)).x;
const uint Ca = (a0 * (64u - weight_vec.x) + a1 * weight_vec.x + 32u) / 64u;
p.x = float(Ca) / 65535.0f;
}
} else {
const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]);
const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]);
const vec4 Cf =
vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64);
p = Cf / 65535.0f;
}
#ifdef VULKAN
imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
#else
imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar);
#endif
}
}
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -5,11 +8,11 @@
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2D depth_tex;
layout(binding = 1) uniform isampler2D stencil_tex;
layout(binding = 1) uniform usampler2D stencil_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r;
gl_FragStencilRefARB = int(textureLod(stencil_tex, texcoord, 0).r);
}
@@ -39,6 +39,55 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
POLYGON, // Patches
};
constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = {
Maxwell::PrimitiveTopology::Points, // Points
Maxwell::PrimitiveTopology::Lines, // Lines
Maxwell::PrimitiveTopology::LineLoop, // LineLoop
Maxwell::PrimitiveTopology::LineStrip, // LineStrip
Maxwell::PrimitiveTopology::Triangles, // Triangles
Maxwell::PrimitiveTopology::Triangles, // TriangleStrip
Maxwell::PrimitiveTopology::Triangles, // TriangleFan
Maxwell::PrimitiveTopology::Triangles, // Quads
Maxwell::PrimitiveTopology::Triangles, // QuadStrip
Maxwell::PrimitiveTopology::Triangles, // Polygon
Maxwell::PrimitiveTopology::LinesAdjacency, // LinesAdjacency
Maxwell::PrimitiveTopology::LinesAdjacency, // LineStripAdjacency
Maxwell::PrimitiveTopology::TrianglesAdjacency, // TrianglesAdjacency
Maxwell::PrimitiveTopology::TrianglesAdjacency, // TriangleStripAdjacency
Maxwell::PrimitiveTopology::Patches, // Patches
};
bool IsDualSourceBlendFactor(Maxwell::Blend::Factor factor) {
using F = Maxwell::Blend::Factor;
switch (factor) {
case F::Source1Color_D3D:
case F::OneMinusSource1Color_D3D:
case F::Source1Alpha_D3D:
case F::OneMinusSource1Alpha_D3D:
case F::Source1Color_GL:
case F::OneMinusSource1Color_GL:
case F::Source1Alpha_GL:
case F::OneMinusSource1Alpha_GL:
return true;
default:
return false;
}
}
bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) {
if (!regs.blend.enable[0]) {
return false;
}
const auto uses_dual_source = [](const auto& blend) {
return IsDualSourceBlendFactor(blend.color_source) ||
IsDualSourceBlendFactor(blend.color_dest) ||
IsDualSourceBlendFactor(blend.alpha_source) ||
IsDualSourceBlendFactor(blend.alpha_dest);
};
return regs.blend_per_target_enabled ? uses_dual_source(regs.blend_per_target[0])
: uses_dual_source(regs.blend);
}
void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) {
std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(),
[](const auto& layout) {
@@ -62,6 +111,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0);
extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0);
extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0);
color_write_enable_dynamic.Assign(features.has_color_write_enable ? 1 : 0);
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
xfb_enabled.Assign(regs.transform_feedback_enabled != 0);
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
@@ -71,8 +121,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() ==
Maxwell::Tessellation::OutputPrimitives::Triangles_CW);
patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
topology.Assign(topology_);
const bool can_collapse_topology_class =
features.has_extended_dynamic_state && features.has_extended_dynamic_state_2;
topology.Assign(can_collapse_topology_class
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
: topology_);
msaa_mode.Assign(regs.anti_alias_samples_mode);
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
raw2 = 0;
@@ -187,6 +242,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
maxwell3d.dirty.flags[Dirty::Blending] = false;
for (size_t index = 0; index < attachments.size(); ++index) {
attachments[index].Refresh(regs, index);
auto& attachment = attachments[index];
if (color_write_enable_dynamic && attachment.mask_r == 0 &&
attachment.mask_g == 0 && attachment.mask_b == 0 &&
attachment.mask_a == 0) {
attachment.mask_r.Assign(1);
attachment.mask_g.Assign(1);
attachment.mask_b.Assign(1);
attachment.mask_a.Assign(1);
}
}
}
}
@@ -31,6 +31,7 @@ struct DynamicFeatures {
bool has_dynamic_state3_logic_op_enable;
bool has_dynamic_state3_line_stipple_enable;
bool has_dynamic_vertex_input;
bool has_color_write_enable;
bool has_provoking_vertex;
bool has_provoking_vertex_first_mode;
bool has_provoking_vertex_last_mode;
@@ -208,6 +209,9 @@ struct FixedPipelineState {
BitField<12, 2, u32> tessellation_spacing;
BitField<14, 1, u32> tessellation_clockwise;
BitField<15, 5, u32> patch_control_points_minus_one;
BitField<20, 1, u32> color_write_enable_dynamic;
BitField<21, 1, u32> attachment0_dual_source_blend;
BitField<24, 4, Maxwell::PrimitiveTopology> topology;
BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode;
@@ -223,6 +223,10 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_UNORM_BLOCK, 0, EAC_R11_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_SNORM_BLOCK, 0, EAC_R11_SNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, 0, EAC_R11G11_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, 0, EAC_R11G11_SNORM) \
/* Depth formats */ \
SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \
@@ -278,7 +282,17 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
}
} else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) {
// Transcode on hardware that doesn't support ETC2 natively
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
if (pixel_format == PixelFormat::EAC_R11_SNORM) {
tuple.format = VK_FORMAT_R8_SNORM;
} else if (pixel_format == PixelFormat::EAC_R11_UNORM) {
tuple.format = VK_FORMAT_R8_UNORM;
} else if (pixel_format == PixelFormat::EAC_R11G11_SNORM) {
tuple.format = VK_FORMAT_R8G8_SNORM;
} else if (pixel_format == PixelFormat::EAC_R11G11_UNORM) {
tuple.format = VK_FORMAT_R8G8_UNORM;
} else {
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
}
bool const attachable = (tuple.usage & usage_attachable) != 0;
bool const storage = (tuple.usage & usage_storage) != 0;
@@ -7,6 +7,7 @@
#pragma once
#include <cstddef>
#include <optional>
#include <boost/container/small_vector.hpp>
@@ -15,6 +16,7 @@
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/types.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -22,6 +24,29 @@ namespace Vulkan {
using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
[[nodiscard]] inline std::optional<PixelFormat> PixelFormatFromImageFormat(
Shader::ImageFormat format) {
switch (format) {
case Shader::ImageFormat::Typeless:
return std::nullopt;
case Shader::ImageFormat::R8_UINT:
return PixelFormat::R8_UINT;
case Shader::ImageFormat::R8_SINT:
return PixelFormat::R8_SINT;
case Shader::ImageFormat::R16_UINT:
return PixelFormat::R16_UINT;
case Shader::ImageFormat::R16_SINT:
return PixelFormat::R16_SINT;
case Shader::ImageFormat::R32_UINT:
return PixelFormat::R32_UINT;
case Shader::ImageFormat::R32G32_UINT:
return PixelFormat::R32G32_UINT;
case Shader::ImageFormat::R32G32B32A32_UINT:
return PixelFormat::R32G32B32A32_UINT;
}
return std::nullopt;
}
[[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) {
return Shader::NumDescriptors(info.constant_buffer_descriptors) +
Shader::NumDescriptors(info.storage_buffers_descriptors) +
@@ -211,8 +236,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
if (sampler.HasLinearFiltering() &&
VideoCore::Surface::IsPixelFormatInteger(image_view.format)) {
vk_sampler = sampler.HandleWithNearestFilter();
}
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
is_rescaled |= element_rescaled;
@@ -84,7 +84,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
} // Anonymous namespace
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
: VideoCommon::BufferBase(null_params), tracker{4096} {
: VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} {
if (runtime.device.HasNullDescriptor()) {
return;
}
@@ -95,12 +95,18 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
scheduler{&runtime.scheduler},
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} {
if (runtime.device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
}
}
void Buffer::MarkUsage(u64 offset, u64 size) noexcept {
tracker.Track(offset, size);
last_usage_tick = scheduler->CurrentTick();
}
VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) {
if (!device) {
// Null buffer supported, return a null descriptor
@@ -384,7 +390,9 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept {
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
it->ResetUsageTracking();
if (scheduler.IsFree(it->LastUsageTick())) {
it->ResetUsageTracking();
}
}
}
@@ -556,7 +564,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
if (index >= device.GetMaxVertexInputBindings()) {
return;
}
if (device.IsExtExtendedDynamicStateSupported()) {
if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) {
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
@@ -596,7 +604,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
if (binding_count == 0) {
return;
}
if (device.IsExtExtendedDynamicStateSupported()) {
if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) {
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data());
});
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -43,14 +43,16 @@ public:
return tracker.IsUsed(offset, size);
}
void MarkUsage(u64 offset, u64 size) noexcept {
tracker.Track(offset, size);
}
void MarkUsage(u64 offset, u64 size) noexcept;
void ResetUsageTracking() noexcept {
tracker.Reset();
}
[[nodiscard]] u64 LastUsageTick() const noexcept {
return last_usage_tick;
}
operator VkBuffer() const noexcept {
return *buffer;
}
@@ -64,9 +66,11 @@ private:
};
const Device* device{};
Scheduler* scheduler{};
vk::Buffer buffer;
std::vector<BufferView> views;
VideoCommon::UsageTracker tracker;
u64 last_usage_tick{};
bool is_null{};
};
@@ -127,6 +131,10 @@ public:
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
void SetVertexInputDynamicState(bool is_active) {
vertex_input_dynamic_state_active = is_active;
}
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
@@ -163,7 +171,11 @@ public:
private:
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
guest_descriptor_queue.AddBuffer(buffer, offset, size);
if (buffer == VK_NULL_HANDLE) {
guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE);
} else {
guest_descriptor_queue.AddBuffer(buffer, offset, size);
}
}
void ReserveNullBuffer();
@@ -185,6 +197,8 @@ private:
bool limit_dynamic_storage_buffers = false;
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
bool vertex_input_dynamic_state_active = false;
};
struct BufferCacheParams {
@@ -26,6 +26,7 @@
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -745,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];
@@ -193,8 +193,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
is_written = desc.is_written;
}
ImageView& image_view = texture_cache.GetImageView(views[index].id);
PixelFormat format{image_view.format};
if constexpr (is_image) {
if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) {
format = *explicit_format;
}
}
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
image_view.BufferSize(), image_view.format,
image_view.BufferSize(), format,
is_written, is_image);
++index;
}
@@ -426,8 +426,14 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
is_written = desc.is_written;
}
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
PixelFormat format{image_view.format};
if constexpr (is_image) {
if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) {
format = *explicit_format;
}
}
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
image_view.BufferSize(), image_view.format,
image_view.BufferSize(), format,
is_written, is_image);
++index;
++texture_buffer_it;
@@ -472,6 +478,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.runtime.SetVertexInputDynamicState(HasDynamicVertexInput());
buffer_cache.BindHostGeometryBuffers(is_indexed);
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
@@ -865,18 +872,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
};
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT
if (!key.state.dynamic_vertex_input) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
}
}
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS
// Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT
if (key.state.dynamic_vertex_input) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
}
@@ -906,6 +912,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
}
// VK_EXT_color_write_enable fallback for fully on/off render targets when EDS3 blending is not available.
if (!key.state.extended_dynamic_state_3_blend && key.state.color_write_enable_dynamic) {
dynamic_states.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT);
}
// EDS3 - Enables (composite: per-feature)
if (key.state.extended_dynamic_state_3_enables) {
if (device.SupportsDynamicState3DepthClampEnable()) {
@@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
VkSemaphore wait_semaphore, u64 host_tick) {
const VkSemaphore timeline_semaphore = *semaphore;
if (device.HasSynchronization2()) {
const std::array<VkCommandBufferSubmitInfo, 2> cmdbuffer_infos{{
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *upload_cmdbuf,
.deviceMask = 0,
},
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *cmdbuf,
.deviceMask = 0,
},
}};
std::array<VkSemaphoreSubmitInfo, 2> signal_infos{{
{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = timeline_semaphore,
.value = host_tick,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
},
{},
}};
u32 num_signal_semaphores = 1;
if (signal_semaphore) {
signal_infos[1] = VkSemaphoreSubmitInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = signal_semaphore,
.value = 0,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
};
num_signal_semaphores = 2;
}
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo wait_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = wait_semaphore,
.value = 0,
.stageMask = static_cast<VkPipelineStageFlags2>(wait_stage_mask),
.deviceIndex = 0,
};
const VkSubmitInfo2 submit_info2{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.pNext = nullptr,
.flags = 0,
.waitSemaphoreInfoCount = num_wait_semaphores,
.pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr,
.commandBufferInfoCount = static_cast<u32>(cmdbuffer_infos.size()),
.pCommandBufferInfos = cmdbuffer_infos.data(),
.signalSemaphoreInfoCount = num_signal_semaphores,
.pSignalSemaphoreInfos = signal_infos.data(),
};
return device.GetGraphicsQueue().Submit2(submit_info2);
}
const u32 num_signal_semaphores = signal_semaphore ? 2 : 1;
const std::array signal_values{host_tick, u64(0)};
const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
@@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
vk::CommandBuffer& upload_cmdbuf,
VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
u64 host_tick) {
if (device.HasSynchronization2()) {
const std::array<VkCommandBufferSubmitInfo, 2> cmdbuffer_infos{{
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *upload_cmdbuf,
.deviceMask = 0,
},
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *cmdbuf,
.deviceMask = 0,
},
}};
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo signal_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = signal_semaphore,
.value = 0,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
};
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo wait_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = wait_semaphore,
.value = 0,
.stageMask = static_cast<VkPipelineStageFlags2>(wait_stage_mask),
.deviceIndex = 0,
};
const VkSubmitInfo2 submit_info2{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.pNext = nullptr,
.flags = 0,
.waitSemaphoreInfoCount = num_wait_semaphores,
.pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr,
.commandBufferInfoCount = static_cast<u32>(cmdbuffer_infos.size()),
.pCommandBufferInfos = cmdbuffer_infos.data(),
.signalSemaphoreInfoCount = num_signal_semaphores,
.pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr,
};
auto fence = GetFreeFence();
auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence);
if (result == VK_SUCCESS) {
std::scoped_lock lock{wait_mutex};
wait_queue.emplace(host_tick, std::move(fence));
wait_cv.notify_one();
}
return result;
}
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
@@ -246,32 +246,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
// Check for dual source blending
const auto& blend0 = key.state.attachments[0];
if (blend0.enable != 0) {
using F = Maxwell::Blend::Factor;
const auto src_rgb = blend0.SourceRGBFactor();
const auto dst_rgb = blend0.DestRGBFactor();
const auto src_a = blend0.SourceAlphaFactor();
const auto dst_a = blend0.DestAlphaFactor();
info.dual_source_blend =
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
}
info.dual_source_blend = key.state.attachment0_dual_source_blend != 0;
if (device.IsMoltenVK()) {
for (size_t i = 0; i < 8; ++i) {
@@ -378,12 +353,30 @@ 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,
.support_descriptor_aliasing = device.IsDescriptorAliasingSupported(),
.support_int8 = device.IsInt8Supported(),
.support_uniform_and_storage_buffer_8bit =
device.IsUniformAndStorageBuffer8BitAccessSupported(),
.support_int16 = device.IsShaderInt16Supported(),
.support_uniform_and_storage_buffer_16bit =
device.IsUniformAndStorageBuffer16BitAccessSupported(),
.support_int64 = device.IsShaderInt64Supported(),
.support_vertex_instance_id = false,
.support_float_controls = device.IsKhrShaderFloatControlsSupported(),
@@ -403,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(),
@@ -500,6 +494,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.IsExtExtendedDynamicState3BlendingSupported();
dynamic_features.has_extended_dynamic_state_3_enables =
device.IsExtExtendedDynamicState3EnablesSupported();
dynamic_features.has_color_write_enable =
device.IsExtColorWriteEnableSupported();
dynamic_features.has_dynamic_state3_depth_clamp_enable =
dynamic_features.has_extended_dynamic_state_3_enables &&
device.SupportsDynamicState3DepthClampEnable();
@@ -632,7 +628,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
dynamic_features.has_extended_dynamic_state_3_blend ||
(key.state.extended_dynamic_state_3_enables != 0) !=
dynamic_features.has_extended_dynamic_state_3_enables ||
(key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) {
(key.state.color_write_enable_dynamic != 0) !=
dynamic_features.has_color_write_enable ||
(key.state.dynamic_vertex_input != 0) !=
dynamic_features.has_dynamic_vertex_input) {
return;
}
@@ -41,8 +41,8 @@ class SamplesQueryBank : public VideoCommon::BankBase {
public:
static constexpr size_t BANK_SIZE = 256;
static constexpr size_t QUERY_SIZE = 8;
explicit SamplesQueryBank(const Device& device_, size_t index_)
: BankBase(BANK_SIZE), device{device_}, index{index_} {
explicit SamplesQueryBank(const Device& device_, Scheduler& scheduler_, size_t index_)
: BankBase(BANK_SIZE), device{device_}, scheduler{scheduler_}, index{index_} {
const auto& dev = device.GetLogical();
query_pool = dev.CreateQueryPool({
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
@@ -60,12 +60,28 @@ public:
void Reset() override {
ASSERT(references == 0);
VideoCommon::BankBase::Reset();
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
if (device.IsHostQueryResetSupported()) {
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
} else {
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([pool = *query_pool](vk::CommandBuffer cmdbuf) {
cmdbuf.ResetQueryPool(pool, 0, BANK_SIZE);
});
}
host_results.fill(0ULL);
next_bank = 0;
}
void AddReference(size_t how_many = 1) {
BankBase::AddReference(how_many);
last_used_tick = scheduler.CurrentTick();
}
[[nodiscard]] bool IsDead() const {
return BankBase::IsDead() && scheduler.IsFree(last_used_tick);
}
void Sync(size_t start, size_t size) {
const auto& dev = device.GetLogical();
const VkResult query_result = dev.GetQueryResults(
@@ -98,9 +114,11 @@ public:
private:
const Device& device;
Scheduler& scheduler;
const size_t index;
vk::QueryPool query_pool;
std::array<u64, BANK_SIZE> host_results;
u64 last_used_tick{};
};
using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
@@ -218,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;
@@ -431,7 +450,7 @@ private:
void ReserveBank() {
current_bank_id =
bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) {
queue.emplace_back(device, index);
queue.emplace_back(device, scheduler, index);
});
if (current_bank) {
current_bank->next_bank = current_bank_id + 1;
@@ -620,6 +639,15 @@ public:
VideoCommon::BankBase::Reset();
}
void AddReference(size_t how_many = 1) {
BankBase::AddReference(how_many);
last_used_tick = scheduler.CurrentTick();
}
[[nodiscard]] bool IsDead() const {
return BankBase::IsDead() && scheduler.IsFree(last_used_tick);
}
void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
@@ -645,6 +673,7 @@ private:
Scheduler& scheduler;
const size_t index;
vk::Buffer buffer;
u64 last_used_tick{};
};
class PrimitivesSucceededStreamer;
@@ -922,7 +951,7 @@ private:
return;
}
has_flushed_end_pending = false;
// Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date.
UpdateBuffers();
if (buffers_count == 0) {
@@ -1492,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;
}
@@ -223,7 +223,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
scheduler.SetQueryCache(query_cache);
}
RasterizerVulkan::~RasterizerVulkan() = default;
RasterizerVulkan::~RasterizerVulkan() {
scheduler.WaitWorker();
scheduler.Finish();
}
template <typename Func>
void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
@@ -1011,12 +1014,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
auto& regs = maxwell3d->regs;
auto& flags = maxwell3d->dirty.flags;
const auto topology = maxwell3d->draw_manager.draw_state.topology;
if (state_tracker.ChangePrimitiveTopology(topology)) {
const bool topology_changed = state_tracker.ChangePrimitiveTopology(topology);
if (topology_changed) {
flags[Dirty::DepthBiasEnable] = true;
flags[Dirty::PrimitiveRestartEnable] = true;
}
// Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -1025,7 +1028,6 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateStencilFaces(regs);
UpdateLineWidth(regs);
// EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest
if (device.IsExtExtendedDynamicStateSupported()) {
UpdateCullMode(regs);
UpdateDepthCompareOp(regs);
@@ -1037,21 +1039,24 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthWriteEnable(regs);
UpdateStencilTestEnable(regs);
}
if (topology_changed) {
scheduler.Record([topology_vk = MaxwellToVK::PrimitiveTopology(device, topology)](
vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveTopologyEXT(topology_vk);
});
}
}
// EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable
if (device.IsExtExtendedDynamicState2Supported()) {
UpdatePrimitiveRestartEnable(regs);
UpdateRasterizerDiscardEnable(regs);
UpdateDepthBiasEnable(regs);
}
// EDS2 Extras: LogicOp operation selection
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
UpdateLogicOp(regs);
}
// EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
using namespace Tegra::Engines;
// AMD Workaround: LogicOp incompatible with float render targets
@@ -1076,12 +1081,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateAlphaToOneEnable(regs);
}
// EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask
if (device.IsExtExtendedDynamicState3BlendingSupported()) {
UpdateBlending(regs);
} else if (device.IsExtColorWriteEnableSupported()) {
UpdateColorWriteEnable(regs);
}
// Vertex Input Dynamic State: Independent from EDS levels
if (device.IsExtVertexInputDynamicStateSupported()) {
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) {
UpdateVertexInput(regs);
@@ -1094,7 +1099,6 @@ void RasterizerVulkan::HandleTransformFeedback() {
const auto& regs = maxwell3d->regs;
if (!device.IsExtTransformFeedbackSupported()) {
// If the guest enabled transform feedback, warn once that the device lacks support.
if (regs.transform_feedback_enabled != 0) {
std::call_once(warn_unsupported, [&] {
LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled");
@@ -1723,9 +1727,16 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) {
if (state_tracker.TouchBlendEnable()) {
std::array<VkBool32, Maxwell::NumRenderTargets> setup_enables{};
std::ranges::transform(
regs.blend.enable, setup_enables.begin(),
[&](const auto& is_enabled) { return is_enabled != 0 ? VK_TRUE : VK_FALSE; });
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
bool is_integer = false;
if (regs.rt[index].format != Tegra::RenderTargetFormat::NONE) {
const auto format =
VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format);
is_integer = IsPixelFormatInteger(format);
}
setup_enables[index] =
(!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE;
}
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
cmdbuf.SetColorBlendEnableEXT(0, setup_enables);
});
@@ -1774,6 +1785,20 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) {
}
}
void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchColorMask()) {
return;
}
std::array<VkBool32, Maxwell::NumRenderTargets> setup_enables{};
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE;
}
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
cmdbuf.SetColorWriteEnableEXT(setup_enables);
});
}
void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchStencilTestEnable()) {
return;
@@ -191,6 +191,7 @@ private:
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateLogicOp(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -344,7 +344,9 @@ void Scheduler::EndRenderPass()
Record([num_images = num_renderpass_images,
images = renderpass_images,
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
ranges = renderpass_image_ranges,
has_transform_feedback = device.IsExtTransformFeedbackSupported()](
vk::CommandBuffer cmdbuf) {
std::array<VkImageMemoryBarrier, 9> barriers;
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
@@ -384,6 +386,17 @@ void Scheduler::EndRenderPass()
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
if (has_transform_feedback) {
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
0, XFB_OUTPUT_BARRIER);
}
});
state.renderpass = VkRenderPass{};
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <optional>
#include <span>
#include <memory>
#include <vector>
@@ -128,9 +129,32 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
return usage;
}
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
const auto format_info =
[[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) {
if (!IsPixelFormatASTC(info.format) || device.IsOptimalAstcSupported()) {
return false;
}
if (Settings::values.accelerate_astc.GetValue() != Settings::AstcDecodeMode::Gpu) {
return false;
}
return Settings::values.astc_recompression.GetValue() ==
Settings::AstcRecompression::Uncompressed &&
info.size.depth == 1;
}
[[nodiscard]] bool WillUseWidenedAstcFormat(const Device& device, const ImageInfo& info) {
return WillUseAcceleratedAstcDecode(device, info) &&
!VideoCore::Surface::IsPixelFormatSRGB(info.format);
}
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info,
std::optional<VkFormat> format_override = {}) {
auto format_info =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format);
if (format_override) {
format_info.format = *format_override;
format_info.attachable = false;
format_info.storage = true;
}
VkImageCreateFlags flags{};
if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
info.size.width == info.size.height && !device.HasBrokenCubeImageCompatibility()) {
@@ -164,11 +188,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
}
[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
const ImageInfo& info, std::span<const VkFormat> view_formats) {
const ImageInfo& info, std::span<const VkFormat> view_formats,
std::optional<VkFormat> format_override = {}) {
if (info.type == ImageType::Buffer) {
return vk::Image{};
}
VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info);
VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override);
const VkImageFormatListCreateInfo image_format_list = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
.pNext = nullptr,
@@ -1299,7 +1324,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
case PixelFormat::R32G32_FLOAT:
case PixelFormat::R32G32_SINT:
case PixelFormat::R32_FLOAT:
if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) {
if (src_view.format == PixelFormat::D32_FLOAT &&
(dst_view.format == PixelFormat::B5G6R5_UNORM ||
Settings::values.fix_bloom_effects.GetValue())) {
const Region2D region{
.start = {0, 0},
.end = {static_cast<s32>(dst->RenderArea().width),
@@ -1555,15 +1582,19 @@ void TextureCacheRuntime::TickFrame() {}
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
VAddr cpu_addr_)
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
runtime->ViewFormats(info.format))),
runtime{&runtime_},
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
WillUseWidenedAstcFormat(runtime_.device, info)
? std::span<const VkFormat>{}
: runtime->ViewFormats(info.format),
WillUseWidenedAstcFormat(runtime_.device, info)
? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT)
: std::nullopt)),
aspect_mask(ImageAspectMask(info.format)) {
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
switch (Settings::values.accelerate_astc.GetValue()) {
case Settings::AstcDecodeMode::Gpu:
if (Settings::values.astc_recompression.GetValue() ==
Settings::AstcRecompression::Uncompressed &&
info.size.depth == 1) {
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
}
break;
@@ -1589,9 +1620,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
Settings::values.astc_recompression.GetValue() ==
Settings::AstcRecompression::Uncompressed) {
const auto& device = runtime->device.GetLogical();
const VkFormat storage_format = WillUseWidenedAstcFormat(runtime->device, info)
? VK_FORMAT_R32G32B32A32_SFLOAT
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
for (s32 level = 0; level < info.resources.levels; ++level) {
storage_image_views[level] =
MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
MakeStorageView(device, level, *original_image, storage_format);
}
}
}
@@ -1601,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
@@ -1620,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,
@@ -1940,8 +1976,13 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
VkImageView Image::StorageImageView(s32 level) noexcept {
auto& view = storage_image_views[level];
if (!view) {
const auto format_info =
auto format_info =
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
format_info.format = WillUseWidenedAstcFormat(runtime->device, info)
? VK_FORMAT_R32G32B32A32_SFLOAT
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
format_info.format);
}
@@ -2108,7 +2149,11 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne());
}
}
const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
uses_widened_astc_format = WillUseWidenedAstcFormat(*device, image.info);
auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
if (uses_widened_astc_format) {
format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
}
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;
@@ -2242,7 +2287,14 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type,
Shader::ImageFormat image_format) {
if (image_handle) {
if (image_format == Shader::ImageFormat::Typeless) {
return Handle(texture_type);
if (!typeless_storage_view) {
auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
if (uses_widened_astc_format) {
info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
}
typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type);
}
return *typeless_storage_view;
}
const bool is_signed = image_format == Shader::ImageFormat::R8_SINT
|| image_format == Shader::ImageFormat::R16_SINT;
@@ -2251,7 +2303,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;
@@ -2261,13 +2313,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,
@@ -2275,7 +2342,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,
});
}
@@ -2297,12 +2364,15 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
const void* pnext = nullptr;
if (has_custom_border_colors) {
pnext = &border_ci;
// Log extension usage for custom border color
if (GPU::Logging::IsActive()) {
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
"VK_EXT_custom_border_color", "Sampler::Sampler");
}
}
if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) {
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
"VK_EXT_border_color_swizzle", "Sampler::Sampler");
}
const VkSamplerReductionModeCreateInfoEXT reduction_ci{
.sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT,
.pNext = pnext,
@@ -2316,20 +2386,28 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
// Some games have samplers with garbage. Sanitize them here.
const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
const auto create_sampler = [&](const f32 anisotropy) {
const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)};
const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)};
const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)};
const bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR ||
min_filter == VK_FILTER_LINEAR ||
mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR};
const auto create_sampler = [&](const f32 anisotropy, bool force_nearest) {
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = pnext,
.flags = 0,
.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
.magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter,
.minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter,
.mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode,
.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
.mipLodBias = tsc.LodBias(),
.anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
.maxAnisotropy = anisotropy,
.anisotropyEnable =
static_cast<VkBool32>(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
.maxAnisotropy = force_nearest ? 1.0f : anisotropy,
.compareEnable = tsc.depth_compare_enabled,
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
@@ -2340,11 +2418,18 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
});
};
sampler = create_sampler(max_anisotropy);
sampler = create_sampler(max_anisotropy, false);
const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy);
if (max_anisotropy > max_anisotropy_default) {
sampler_default_anisotropy = create_sampler(max_anisotropy_default);
sampler_default_anisotropy = create_sampler(max_anisotropy_default, false);
}
if (has_linear_filtering) {
// Integer-format image views can never be linearly filtered
// (VUID-vkCmdDraw*-magFilter-04553); this sampler is cached purely from the guest's TSC,
// decoupled from whichever ImageView it ends up paired with, so build a nearest-forced
// fallback here for callers to swap to when the paired view turns out to be integer.
sampler_nearest = create_sampler(1.0f, true);
}
}
@@ -370,13 +370,15 @@ 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;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
std::optional<StorageViews> storage_views;
vk::ImageView typeless_storage_view;
vk::ImageView depth_view;
vk::ImageView stencil_view;
vk::ImageView color_view;
@@ -385,6 +387,8 @@ private:
VkImageView render_target = VK_NULL_HANDLE;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
u32 buffer_size = 0;
bool uses_widened_astc_format = false;
};
class ImageAlloc : public VideoCommon::ImageAllocBase {};
@@ -405,9 +409,18 @@ public:
return static_cast<bool>(sampler_default_anisotropy);
}
[[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
return *sampler_nearest;
}
[[nodiscard]] bool HasLinearFiltering() const noexcept {
return static_cast<bool>(sampler_nearest);
}
private:
vk::Sampler sampler;
vk::Sampler sampler_default_anisotropy;
vk::Sampler sampler_nearest;
};
struct TextureCacheParams {
+4
View File
@@ -344,6 +344,10 @@ bool IsPixelFormatETC2(PixelFormat format) {
case PixelFormat::ETC2_RGB_SRGB:
case PixelFormat::ETC2_RGBA_SRGB:
case PixelFormat::ETC2_RGB_PTA_SRGB:
case PixelFormat::EAC_R11_UNORM:
case PixelFormat::EAC_R11_SNORM:
case PixelFormat::EAC_R11G11_UNORM:
case PixelFormat::EAC_R11G11_SNORM:
return true;
default:
return false;
+4
View File
@@ -119,6 +119,10 @@ namespace VideoCore::Surface {
PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \
PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \
PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \
PIXEL_FORMAT_ELEM(EAC_R11_UNORM, 4, 4, 64) \
PIXEL_FORMAT_ELEM(EAC_R11_SNORM, 4, 4, 64) \
PIXEL_FORMAT_ELEM(EAC_R11G11_UNORM, 4, 4, 128) \
PIXEL_FORMAT_ELEM(EAC_R11G11_SNORM, 4, 4, 128) \
/* Depth formats */ \
PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \
PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \
@@ -204,6 +204,15 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::ETC2_RGB_PTA_SRGB;
case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB):
return PixelFormat::ETC2_RGBA_SRGB;
/* EAC */
case Hash(TextureFormat::EAC, UNORM):
return PixelFormat::EAC_R11_UNORM;
case Hash(TextureFormat::EAC, SNORM):
return PixelFormat::EAC_R11_SNORM;
case Hash(TextureFormat::EACX2, UNORM):
return PixelFormat::EAC_R11G11_UNORM;
case Hash(TextureFormat::EACX2, SNORM):
return PixelFormat::EAC_R11G11_SNORM;
/* ASTC */
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR):
return PixelFormat::ASTC_2D_4X4_UNORM;
+361 -10
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill
@@ -1269,6 +1269,220 @@ static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount,
return SelectPartition(seed, x, y, 0, partitionCount, smallBlock);
}
static constexpr bool IsHDRColorEndpointMode(u32 cem) {
switch (cem) {
case 2:
case 3:
case 7:
case 11:
case 14:
case 15:
return true;
default:
return false;
}
}
// Sign-extends the low nbits of value (a 2's complement field packed into
// the bottom of an otherwise-unsigned integer), per C.2.15's HDR endpoint
// bitfield unpacking.
static constexpr s32 SignExtend(s32 value, u32 nbits) {
const s32 sign_bit = 1 << (nbits - 1);
return (value ^ sign_bit) - sign_bit;
}
struct HDREndpointRGB {
s32 r0, g0, b0;
s32 r1, g1, b1;
};
// HDR Endpoint Mode 7 (C.2.15): base RGB + scale factor.
static void DecodeHDREndpointMode7(u32 v0, u32 v1, u32 v2, u32 v3, s32& r0, s32& g0, s32& b0,
s32& r1, s32& g1, s32& b1) {
const u32 modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4);
u32 majcomp;
u32 mode;
if ((modeval & 0xC) != 0xC) {
majcomp = modeval >> 2;
mode = modeval & 3;
} else if (modeval != 0xF) {
majcomp = modeval & 3;
mode = 4;
} else {
majcomp = 0;
mode = 5;
}
s32 red = static_cast<s32>(v0 & 0x3f);
s32 green = static_cast<s32>(v1 & 0x1f);
s32 blue = static_cast<s32>(v2 & 0x1f);
s32 scale = static_cast<s32>(v3 & 0x1f);
const u32 x0 = (v1 >> 6) & 1;
const u32 x1 = (v1 >> 5) & 1;
const u32 x2 = (v2 >> 6) & 1;
const u32 x3 = (v2 >> 5) & 1;
const u32 x4 = (v3 >> 7) & 1;
const u32 x5 = (v3 >> 6) & 1;
const u32 x6 = (v3 >> 5) & 1;
const u32 ohm = 1u << mode;
if (ohm & 0x30)
green |= static_cast<s32>(x0 << 6);
if (ohm & 0x3A)
green |= static_cast<s32>(x1 << 5);
if (ohm & 0x30)
blue |= static_cast<s32>(x2 << 6);
if (ohm & 0x3A)
blue |= static_cast<s32>(x3 << 5);
if (ohm & 0x3D)
scale |= static_cast<s32>(x6 << 5);
if (ohm & 0x2D)
scale |= static_cast<s32>(x5 << 6);
if (ohm & 0x04)
scale |= static_cast<s32>(x4 << 7);
if (ohm & 0x3B)
red |= static_cast<s32>(x4 << 6);
if (ohm & 0x04)
red |= static_cast<s32>(x3 << 6);
if (ohm & 0x10)
red |= static_cast<s32>(x5 << 7);
if (ohm & 0x0F)
red |= static_cast<s32>(x2 << 7);
if (ohm & 0x05)
red |= static_cast<s32>(x1 << 8);
if (ohm & 0x0A)
red |= static_cast<s32>(x0 << 8);
if (ohm & 0x05)
red |= static_cast<s32>(x0 << 9);
if (ohm & 0x02)
red |= static_cast<s32>(x6 << 9);
if (ohm & 0x01)
red |= static_cast<s32>(x3 << 10);
if (ohm & 0x02)
red |= static_cast<s32>(x5 << 10);
static constexpr int shamts[6] = {1, 1, 2, 3, 4, 5};
const s32 shamt = shamts[mode];
red <<= shamt;
green <<= shamt;
blue <<= shamt;
scale <<= shamt;
if (mode != 5) {
green = red - green;
blue = red - blue;
}
if (majcomp == 1)
std::swap(red, green);
if (majcomp == 2)
std::swap(red, blue);
r1 = std::clamp(red, 0, 0xFFF);
g1 = std::clamp(green, 0, 0xFFF);
b1 = std::clamp(blue, 0, 0xFFF);
r0 = std::clamp(red - scale, 0, 0xFFF);
g0 = std::clamp(green - scale, 0, 0xFFF);
b0 = std::clamp(blue - scale, 0, 0xFFF);
}
// HDR Endpoint Mode 11 (C.2.15): direct RGB pair. Shared by modes 11, 14 and 15,
// which all decode their RGB the same way and only differ in how alpha is filled in.
static HDREndpointRGB DecodeHDREndpointMode11(u32 v0, u32 v1, u32 v2, u32 v3, u32 v4, u32 v5) {
const u32 majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6);
if (majcomp == 3) {
HDREndpointRGB result;
result.r0 = static_cast<s32>(v0 << 4);
result.g0 = static_cast<s32>(v2 << 4);
result.b0 = static_cast<s32>((v4 & 0x7f) << 5);
result.r1 = static_cast<s32>(v1 << 4);
result.g1 = static_cast<s32>(v3 << 4);
result.b1 = static_cast<s32>((v5 & 0x7f) << 5);
return result;
}
const u32 mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5);
s32 va = static_cast<s32>(v0 | ((v1 & 0x40) << 2));
s32 vb0 = static_cast<s32>(v2 & 0x3f);
s32 vb1 = static_cast<s32>(v3 & 0x3f);
s32 vc = static_cast<s32>(v1 & 0x3f);
s32 vd0 = static_cast<s32>(v4 & 0x7f);
s32 vd1 = static_cast<s32>(v5 & 0x7f);
static constexpr int dbitstab[8] = {7, 6, 7, 6, 5, 6, 5, 6};
vd0 = SignExtend(vd0, dbitstab[mode]);
vd1 = SignExtend(vd1, dbitstab[mode]);
const u32 x0 = (v2 >> 6) & 1;
const u32 x1 = (v3 >> 6) & 1;
const u32 x2 = (v4 >> 6) & 1;
const u32 x3 = (v5 >> 6) & 1;
const u32 x4 = (v4 >> 5) & 1;
const u32 x5 = (v5 >> 5) & 1;
const u32 ohm = 1u << mode;
if (ohm & 0xA4)
va |= static_cast<s32>(x0 << 9);
if (ohm & 0x08)
va |= static_cast<s32>(x2 << 9);
if (ohm & 0x50)
va |= static_cast<s32>(x4 << 9);
if (ohm & 0x50)
va |= static_cast<s32>(x5 << 10);
if (ohm & 0xA0)
va |= static_cast<s32>(x1 << 10);
if (ohm & 0xC0)
va |= static_cast<s32>(x2 << 11);
if (ohm & 0x04)
vc |= static_cast<s32>(x1 << 6);
if (ohm & 0xE8)
vc |= static_cast<s32>(x3 << 6);
if (ohm & 0x20)
vc |= static_cast<s32>(x2 << 7);
if (ohm & 0x5B)
vb0 |= static_cast<s32>(x0 << 6);
if (ohm & 0x5B)
vb1 |= static_cast<s32>(x1 << 6);
if (ohm & 0x12)
vb0 |= static_cast<s32>(x2 << 7);
if (ohm & 0x12)
vb1 |= static_cast<s32>(x3 << 7);
// NOTE: the published spec text says "modeval >> 1" here, but no "modeval" is defined
// in this decode (that name belongs to Mode 7's unrelated decode) -- substituting the
// "mode" computed just above reproduces exactly Table C.2.23's per-mode shift amounts
// (3,3,2,2,1,1,0,0 for modes 0..7), so this is a spec transcription error, not a real
// "modeval" this function forgot to compute.
const s32 shamt = (static_cast<s32>(mode) >> 1) ^ 3;
va <<= shamt;
vb0 <<= shamt;
vb1 <<= shamt;
vc <<= shamt;
vd0 <<= shamt;
vd1 <<= shamt;
HDREndpointRGB result;
result.r1 = std::clamp(va, 0, 0xFFF);
result.g1 = std::clamp(va - vb0, 0, 0xFFF);
result.b1 = std::clamp(va - vb1, 0, 0xFFF);
result.r0 = std::clamp(va - vc, 0, 0xFFF);
result.g0 = std::clamp(va - vb0 - vc - vd0, 0, 0xFFF);
result.b0 = std::clamp(va - vb1 - vc - vd1, 0, 0xFFF);
if (majcomp == 1) {
std::swap(result.r0, result.g0);
std::swap(result.r1, result.g1);
} else if (majcomp == 2) {
std::swap(result.r0, result.b0);
std::swap(result.r1, result.b1);
}
return result;
}
// Section C.2.14
static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
u32 colorEndpointMode) {
@@ -1382,8 +1596,85 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
ep2.ClampByte();
} break;
case 2: {
READ_UINT_VALUES(2)
u32 y0, y1;
if (v[1] >= v[0]) {
y0 = v[0] << 4;
y1 = v[1] << 4;
} else {
y0 = (v[1] << 4) + 8;
y1 = (v[0] << 4) - 8;
}
ep1 = Pixel(0x780, y0, y0, y0);
ep2 = Pixel(0x780, y1, y1, y1);
} break;
case 3: {
READ_UINT_VALUES(2)
u32 y0, d;
if (v[0] & 0x80) {
y0 = ((v[1] & 0xE0) << 4) | ((v[0] & 0x7F) << 2);
d = (v[1] & 0x1F) << 2;
} else {
y0 = ((v[1] & 0xF0) << 4) | ((v[0] & 0x7F) << 1);
d = (v[1] & 0x0F) << 1;
}
const u32 y1 = (std::min)(y0 + d, 0xFFFU);
ep1 = Pixel(0x780, y0, y0, y0);
ep2 = Pixel(0x780, y1, y1, y1);
} break;
case 7: {
READ_UINT_VALUES(4)
s32 r0, g0, b0, r1, g1, b1;
DecodeHDREndpointMode7(v[0], v[1], v[2], v[3], r0, g0, b0, r1, g1, b1);
ep1 = Pixel(0x780, r0, g0, b0);
ep2 = Pixel(0x780, r1, g1, b1);
} break;
case 11: {
READ_UINT_VALUES(6)
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
ep1 = Pixel(0x780, rgb.r0, rgb.g0, rgb.b0);
ep2 = Pixel(0x780, rgb.r1, rgb.g1, rgb.b1);
} break;
case 14: {
READ_UINT_VALUES(8)
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
// Only mode with LDR (8-bit UNORM)-interpreted alpha; left as-is (0-255).
ep1 = Pixel(v[6], rgb.r0, rgb.g0, rgb.b0);
ep2 = Pixel(v[7], rgb.r1, rgb.g1, rgb.b1);
} break;
case 15: {
READ_UINT_VALUES(8)
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
const u32 mode = ((v[6] >> 7) & 1) | ((v[7] >> 6) & 2);
s32 a6 = static_cast<s32>(v[6] & 0x7F);
s32 a7 = static_cast<s32>(v[7] & 0x7F);
s32 alpha0, alpha1;
if (mode == 3) {
alpha0 = a6 << 5;
alpha1 = a7 << 5;
} else {
a6 |= (a7 << (mode + 1)) & 0x780;
a7 &= (0x3F >> mode);
a7 ^= 0x20 >> mode;
a7 -= 0x20 >> mode;
a6 <<= (4 - mode);
a7 <<= (4 - mode);
a7 += a6;
alpha0 = a6;
alpha1 = std::clamp(a7, 0, 0xFFF);
}
ep1 = Pixel(alpha0, rgb.r0, rgb.g0, rgb.b0);
ep2 = Pixel(alpha1, rgb.r1, rgb.g1, rgb.b1);
} break;
default:
assert(false && "Unsupported color endpoint mode (is it HDR?)");
assert(false && "Unsupported color endpoint mode");
break;
}
@@ -1414,6 +1705,39 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
}
}
static float HalfToFloat(u16 h) {
const u32 sign = static_cast<u32>(h & 0x8000) << 16;
u32 exp = (h & 0x7C00) >> 10;
u32 mant = h & 0x3FF;
u32 bits;
if (exp == 0) {
if (mant == 0) {
bits = sign;
} else {
s32 e = 127 - 15 + 1;
while ((mant & 0x400) == 0) {
mant <<= 1;
--e;
}
mant &= 0x3FF;
bits = sign | (static_cast<u32>(e) << 23) | (mant << 13);
}
} else if (exp == 0x1F) {
bits = sign | 0x7F800000 | (mant << 13);
} else {
bits = sign | ((exp - 15 + 127) << 23) | (mant << 13);
}
float result;
std::memcpy(&result, &bits, sizeof(result));
return result;
}
static u16 HalfToClampedByte(u16 half_bits) {
const float value = HalfToFloat(half_bits);
const float clamped = std::clamp(value, 0.0f, 1.0f);
return static_cast<u16>(clamped * 255.0f + 0.5f);
}
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
for (u32 j = 0; j < blockHeight; j++) {
for (u32 i = 0; i < blockWidth; i++) {
@@ -1631,22 +1955,49 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
Pixel p;
for (u32 c = 0; c < 4; c++) {
u32 C0 = endpoints[partition][0].Component(c);
C0 = ReplicateByteTo16(C0);
u32 C1 = endpoints[partition][1].Component(c);
C1 = ReplicateByteTo16(C1);
u32 plane = 0;
if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) {
plane = 1;
}
u32 weight = weights[plane][j * blockWidth + i];
u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
if (C == 65535) {
p.Component(c) = 255;
// Mode 14 is RGB-HDR but keeps an LDR (8-bit UNORM)-interpreted alpha
// (component 0 here, see Pixel::A()) -- the only HDR mode with this split.
const bool is_hdr = IsHDRColorEndpointMode(colorEndpointMode[partition]) &&
!(colorEndpointMode[partition] == 14 && c == 0);
if (is_hdr) {
// Endpoints are raw 12-bit pseudo-logarithmic values; shift left 4 bits
// to become 16-bit before interpolating, per C.2.19.
C0 <<= 4;
C1 <<= 4;
const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
const u32 E = (C & 0xF800) >> 11;
const u32 M = C & 0x7FF;
u32 Mt;
if (M < 512) {
Mt = 3 * M;
} else if (M >= 1536) {
Mt = 5 * M - 2048;
} else {
Mt = 4 * M - 512;
}
const u32 Cf = (E << 10) + (Mt >> 3);
// +Inf/NaN clamps to the largest finite FP16 value (0x7BFF).
const u16 half_bits = (Cf >= 0x7C00) ? u16{0x7BFF} : static_cast<u16>(Cf);
p.Component(c) = HalfToClampedByte(half_bits);
} else {
double Cf = static_cast<double>(C);
p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5);
C0 = ReplicateByteTo16(C0);
C1 = ReplicateByteTo16(C1);
const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
if (C == 65535) {
p.Component(c) = 255;
} else {
double Cf = static_cast<double>(C);
p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5);
}
}
}
+46 -21
View File
@@ -95,6 +95,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
VK_FORMAT_UNDEFINED,
};
constexpr std::array B10G11R11_UFLOAT_PACK32{
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
VK_FORMAT_UNDEFINED,
};
} // namespace Alternatives
template <typename T>
@@ -127,6 +133,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
return Alternatives::B10G11R11_UFLOAT_PACK32.data();
default:
return nullptr;
}
@@ -292,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) {
@@ -492,18 +504,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
CollectToolingInfo();
if (is_qualcomm) {
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers require scaled vertex format emulation");
LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation");
must_emulate_scaled_formats = true;
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have broken provoking vertex");
RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have slow push descriptor implementation");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan,
"Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers");
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.");
RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64,
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
features.shader_atomic_int64.shaderBufferInt64Atomics = false;
@@ -561,7 +575,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
features.shader_float16_int8.shaderFloat16 = false;
}
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
// Use hardware depth/stencil blits instead when available
if (!extensions.shader_stencil_export) {
LOG_INFO(Render_Vulkan,
@@ -657,14 +670,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
const auto dyna_state = Settings::values.dyna_state.GetValue();
// Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp
// This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs:
// Level 0 = Core Dynamic States only (Vulkan 1.0)
// Level 1 = Core + VK_EXT_extended_dynamic_state
// Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2
// Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3
switch (dyna_state) {
case Settings::ExtendedDynamicState::Disabled:
// Level 0: Disable all extended dynamic state extensions
@@ -699,8 +704,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
break;
}
// VK_EXT_vertex_input_dynamic_state is independent from EDS
// It can be enabled even without extended_dynamic_state
// VK_EXT_vertex_input_dynamic_state
if (!Settings::values.vertex_input_dynamic_state.GetValue()) {
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
@@ -1170,6 +1174,11 @@ bool Device::GetSuitability(bool requires_swapchain) {
}
void Device::RemoveUnsuitableExtensions() {
// VK_EXT_color_write_enable
extensions.color_write_enable = features.color_write_enable.colorWriteEnable;
RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable,
VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
// VK_EXT_custom_border_color
if (extensions.custom_border_color) {
extensions.custom_border_color =
@@ -1179,6 +1188,17 @@ void Device::RemoveUnsuitableExtensions() {
RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
// VK_EXT_border_color_swizzle
if (extensions.border_color_swizzle) {
extensions.border_color_swizzle =
extensions.custom_border_color &&
features.border_color_swizzle.borderColorSwizzle &&
features.border_color_swizzle.borderColorSwizzleFromImage;
}
RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle,
features.border_color_swizzle,
VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME);
// VK_EXT_depth_bias_control
extensions.depth_bias_control =
features.depth_bias_control.depthBiasControl &&
@@ -1375,6 +1395,11 @@ void Device::RemoveUnsuitableExtensions() {
// VK_KHR_maintenance8
extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
// VK_KHR_synchronization2
extensions.synchronization2 = features.synchronization2.synchronization2;
RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
}
void Device::SetupFamilies(VkSurfaceKHR surface) {
+60 -5
View File
@@ -43,12 +43,15 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \
shader_demote_to_helper_invocation) \
FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \
FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4)
FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) \
FEATURE(KHR, Synchronization2, SYNCHRONIZATION_2, synchronization2)
#define FOR_EACH_VK_FEATURE_1_4(FEATURE)
// Define all features which may be used by the implementation and require an extension here.
#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
FEATURE(EXT, BorderColorSwizzle, BORDER_COLOR_SWIZZLE, border_color_swizzle) \
FEATURE(EXT, ColorWriteEnable, COLOR_WRITE_ENABLE, color_write_enable) \
FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
@@ -69,7 +72,9 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \
pipeline_executable_properties) \
FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \
workgroup_memory_explicit_layout)
workgroup_memory_explicit_layout) \
FEATURE(EXT, TextureCompressionASTCHDR, TEXTURE_COMPRESSION_ASTC_HDR, \
texture_compression_astc_hdr)
// Define miscellaneous extensions which may be used by the implementation here.
@@ -180,6 +185,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE_NAME(robustness2, nullDescriptor) \
FEATURE_NAME(shader_float16_int8, shaderFloat16) \
FEATURE_NAME(shader_float16_int8, shaderInt8) \
FEATURE_NAME(synchronization2, synchronization2) \
FEATURE_NAME(timeline_semaphore, timelineSemaphore) \
FEATURE_NAME(transform_feedback, transformFeedback) \
FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \
@@ -344,9 +350,10 @@ FN_MAX_LIMIT_LIST
return properties.float_controls;
}
/// Returns true if ASTC is natively supported.
/// Returns true if ASTC is natively supported, including HDR-profile (non-LDR-only)
bool IsOptimalAstcSupported() const {
return features.features.textureCompressionASTC_LDR;
return features.features.textureCompressionASTC_LDR &&
features.texture_compression_astc_hdr.textureCompressionASTC_HDR;
}
/// Returns true if BCn is natively supported.
@@ -383,6 +390,26 @@ FN_MAX_LIMIT_LIST
return features.shader_float16_int8.shaderInt8;
}
/// Returns true if the device allows 8-bit integer members in uniform/storage buffers.
bool IsUniformAndStorageBuffer8BitAccessSupported() const {
return features.bit8_storage.uniformAndStorageBuffer8BitAccess;
}
/// Returns true if the device allows 16-bit integer members in uniform/storage buffers.
bool IsUniformAndStorageBuffer16BitAccessSupported() const {
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;
@@ -403,6 +430,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;
@@ -509,7 +540,6 @@ FN_MAX_LIMIT_LIST
}
/// Returns true if the device supports VK_EXT_shader_stencil_export.
/// Note: Most Mali/NVIDIA drivers don't support this. Use hardware blits as fallback.
bool IsExtShaderStencilExportSupported() const {
return extensions.shader_stencil_export;
}
@@ -546,6 +576,11 @@ FN_MAX_LIMIT_LIST
return extensions.subgroup_size_control;
}
/// Returns true if vkResetQueryPool (host-side query reset) is supported.
bool IsHostQueryResetSupported() const {
return features.host_query_reset.hostQueryReset != VK_FALSE;
}
/// Returns true if the device supports VK_EXT_transform_feedback.
bool IsExtTransformFeedbackSupported() const {
return extensions.transform_feedback;
@@ -582,6 +617,21 @@ FN_MAX_LIMIT_LIST
return features.custom_border_color.customBorderColorWithoutFormat;
}
/// Returns true if the device supports VK_EXT_color_write_enable.
bool IsExtColorWriteEnableSupported() const {
return extensions.color_write_enable;
}
/// Returns true if the device supports VK_EXT_border_color_swizzle.
bool IsExtBorderColorSwizzleSupported() const {
return extensions.border_color_swizzle;
}
/// Returns true if borderColorSwizzleFromImage is available.
bool IsBorderColorSwizzleFromImageSupported() const {
return features.border_color_swizzle.borderColorSwizzleFromImage;
}
/// Returns true if the device supports VK_EXT_extended_dynamic_state.
bool IsExtExtendedDynamicStateSupported() const {
return extensions.extended_dynamic_state;
@@ -722,6 +772,11 @@ FN_MAX_LIMIT_LIST
bool HasTimelineSemaphore() const;
/// Returns true if the device supports VK_KHR_synchronization2.
bool HasSynchronization2() const {
return extensions.synchronization2;
}
/// Returns the minimum supported version of SPIR-V.
u32 SupportedSpirvVersion() const {
if (instance_version >= VK_API_VERSION_1_3) {
@@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdEndDebugUtilsLabelEXT);
X(vkCmdFillBuffer);
X(vkCmdPipelineBarrier);
X(vkCmdPipelineBarrier2);
X(vkCmdPushConstants);
X(vkCmdPushDescriptorSetWithTemplateKHR);
X(vkCmdSetBlendConstants);
@@ -161,8 +162,10 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdSetStencilTestEnableEXT);
X(vkCmdSetVertexInputEXT);
X(vkCmdSetColorWriteMaskEXT);
X(vkCmdSetColorWriteEnableEXT);
X(vkCmdSetColorBlendEnableEXT);
X(vkCmdSetColorBlendEquationEXT);
X(vkCmdResetQueryPool);
X(vkCmdResolveImage);
X(vkCreateBuffer);
X(vkCreateBufferView);
@@ -226,6 +229,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetSemaphoreCounterValue);
X(vkMapMemory);
X(vkQueueSubmit);
X(vkQueueSubmit2);
X(vkResetFences);
X(vkResetQueryPool);
X(vkSetDebugUtilsObjectNameEXT);
@@ -252,6 +256,14 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
Proc(dld.vkCmdDrawIndirectCount, dld, "vkCmdDrawIndirectCountKHR", device);
Proc(dld.vkCmdDrawIndexedIndirectCount, dld, "vkCmdDrawIndexedIndirectCountKHR", device);
}
// Synchronization2 is core in Vulkan 1.3, otherwise requires VK_KHR_synchronization2
if (!dld.vkCmdPipelineBarrier2) {
Proc(dld.vkCmdPipelineBarrier2, dld, "vkCmdPipelineBarrier2KHR", device);
}
if (!dld.vkQueueSubmit2) {
Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device);
}
#undef X
}
@@ -6,6 +6,7 @@
#pragma once
#include <array>
#include <exception>
#include <limits>
#include <memory>
@@ -237,8 +238,10 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{};
PFN_vkCmdFillBuffer vkCmdFillBuffer{};
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{};
PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{};
PFN_vkCmdPushConstants vkCmdPushConstants{};
PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{};
PFN_vkCmdResetQueryPool vkCmdResetQueryPool{};
PFN_vkCmdResolveImage vkCmdResolveImage{};
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
@@ -275,6 +278,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{};
PFN_vkCmdSetViewport vkCmdSetViewport{};
PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{};
PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT{};
PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{};
PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT{};
PFN_vkCmdWaitEvents vkCmdWaitEvents{};
@@ -340,6 +344,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
PFN_vkMapMemory vkMapMemory{};
PFN_vkQueueSubmit vkQueueSubmit{};
PFN_vkQueueSubmit2 vkQueueSubmit2{};
PFN_vkResetFences vkResetFences{};
PFN_vkResetQueryPool vkResetQueryPool{};
PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{};
@@ -819,6 +824,13 @@ public:
return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
}
/// Submits using VK_KHR_synchronization2 / Vulkan 1.3 vkQueueSubmit2.
/// Only valid to call when the device dispatch table has vkQueueSubmit2 loaded.
VkResult Submit2(Span<VkSubmitInfo2> submit_infos,
VkFence fence = VK_NULL_HANDLE) const noexcept {
return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence);
}
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
return dld->vkQueuePresentKHR(queue, &present_info);
}
@@ -1180,6 +1192,10 @@ public:
dld->vkCmdEndQuery(handle, query_pool, query);
}
void ResetQueryPool(VkQueryPool query_pool, u32 first, u32 count) const noexcept {
dld->vkCmdResetQueryPool(handle, query_pool, first, count);
}
void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first,
Span<VkDescriptorSet> sets, Span<u32> dynamic_offsets) const noexcept {
dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(),
@@ -1287,6 +1303,74 @@ public:
VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
Span<VkBufferMemoryBarrier> buffer_barriers,
Span<VkImageMemoryBarrier> image_barriers) const noexcept {
// Legacy VkPipelineStageFlagBits/VkAccessFlagBits are bit-compatible with their
// Synchronization2 *2 counterparts, so barriers can be widened without a lookup table.
static constexpr u32 MaxBarriers = 16;
if (dld->vkCmdPipelineBarrier2 && memory_barriers.size() <= MaxBarriers &&
buffer_barriers.size() <= MaxBarriers && image_barriers.size() <= MaxBarriers) {
const auto src_stage_mask2 = static_cast<VkPipelineStageFlags2>(src_stage_mask);
const auto dst_stage_mask2 = static_cast<VkPipelineStageFlags2>(dst_stage_mask);
std::array<VkMemoryBarrier2, MaxBarriers> memory_barriers2;
for (u32 i = 0; i < memory_barriers.size(); ++i) {
memory_barriers2[i] = VkMemoryBarrier2{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
.pNext = nullptr,
.srcStageMask = src_stage_mask2,
.srcAccessMask = static_cast<VkAccessFlags2>(memory_barriers[i].srcAccessMask),
.dstStageMask = dst_stage_mask2,
.dstAccessMask = static_cast<VkAccessFlags2>(memory_barriers[i].dstAccessMask),
};
}
std::array<VkBufferMemoryBarrier2, MaxBarriers> buffer_barriers2;
for (u32 i = 0; i < buffer_barriers.size(); ++i) {
const auto& barrier = buffer_barriers[i];
buffer_barriers2[i] = VkBufferMemoryBarrier2{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.pNext = nullptr,
.srcStageMask = src_stage_mask2,
.srcAccessMask = static_cast<VkAccessFlags2>(barrier.srcAccessMask),
.dstStageMask = dst_stage_mask2,
.dstAccessMask = static_cast<VkAccessFlags2>(barrier.dstAccessMask),
.srcQueueFamilyIndex = barrier.srcQueueFamilyIndex,
.dstQueueFamilyIndex = barrier.dstQueueFamilyIndex,
.buffer = barrier.buffer,
.offset = barrier.offset,
.size = barrier.size,
};
}
std::array<VkImageMemoryBarrier2, MaxBarriers> image_barriers2;
for (u32 i = 0; i < image_barriers.size(); ++i) {
const auto& barrier = image_barriers[i];
image_barriers2[i] = VkImageMemoryBarrier2{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.pNext = nullptr,
.srcStageMask = src_stage_mask2,
.srcAccessMask = static_cast<VkAccessFlags2>(barrier.srcAccessMask),
.dstStageMask = dst_stage_mask2,
.dstAccessMask = static_cast<VkAccessFlags2>(barrier.dstAccessMask),
.oldLayout = barrier.oldLayout,
.newLayout = barrier.newLayout,
.srcQueueFamilyIndex = barrier.srcQueueFamilyIndex,
.dstQueueFamilyIndex = barrier.dstQueueFamilyIndex,
.image = barrier.image,
.subresourceRange = barrier.subresourceRange,
};
}
const VkDependencyInfo dependency_info{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pNext = nullptr,
.dependencyFlags = dependency_flags,
.memoryBarrierCount = memory_barriers.size(),
.pMemoryBarriers = memory_barriers2.data(),
.bufferMemoryBarrierCount = buffer_barriers.size(),
.pBufferMemoryBarriers = buffer_barriers2.data(),
.imageMemoryBarrierCount = image_barriers.size(),
.pImageMemoryBarriers = image_barriers2.data(),
};
dld->vkCmdPipelineBarrier2(handle, &dependency_info);
return;
}
dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags,
memory_barriers.size(), memory_barriers.data(),
buffer_barriers.size(), buffer_barriers.data(),
@@ -1511,6 +1595,10 @@ public:
dld->vkCmdSetColorWriteMaskEXT(handle, first, masks.size(), masks.data());
}
void SetColorWriteEnableEXT(Span<VkBool32> enables) const noexcept {
dld->vkCmdSetColorWriteEnableEXT(handle, enables.size(), enables.data());
}
void SetColorBlendEnableEXT(u32 first, Span<VkBool32> enables) const noexcept {
dld->vkCmdSetColorBlendEnableEXT(handle, first, enables.size(), enables.data());
}