mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 04:24:31 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8c6189d53 | |||
| 940cc8caef | |||
| c57fdebdf0 | |||
| 059d902ca3 | |||
| 6df0089950 | |||
| d1f41a91ec | |||
| a1b7cd51b4 | |||
| 6a3f885e46 | |||
| 18a9d0762b | |||
| eb1060381a | |||
| b270bae970 | |||
| b54be98731 | |||
| 0e1e0d5664 | |||
| 0b17ea1804 | |||
| 06580cc6f9 | |||
| bb63996182 | |||
| 83e1a676c5 | |||
| 21fa6b5805 | |||
| 62e8666eee | |||
| ca9ff1642f | |||
| ace66343ce | |||
| 4bf8828df1 | |||
| 7307518e44 | |||
| 8890b8eb2a | |||
| a7d274e5bd | |||
| bfdc50ee76 | |||
| b0ccfb2c2a | |||
| f191d7a0b7 | |||
| 0a0156a484 | |||
| e48a8d3ff4 | |||
| 811ee522e0 | |||
| 7cb8ae0c0d | |||
| dbb48c4e4e | |||
| 8cc658e5be | |||
| 785461a26c | |||
| 7bf0e49a91 | |||
| 55627c3896 | |||
| 1987384dec | |||
| 3b617a6733 | |||
| 963e1f633b | |||
| 6eec277428 | |||
| dbd59bd119 | |||
| 3dc15a4f96 | |||
| 4900fee536 | |||
| 34df1b4a16 | |||
| f5560deab7 | |||
| 307a32f9a0 | |||
| 7bb4991edd | |||
| f5720663c2 | |||
| 53927e175e | |||
| 20a68e1e9d | |||
| c7668fd43e | |||
| eb7f950322 | |||
| 80609ee974 | |||
| 28630ff109 | |||
| b049f7d273 | |||
| f6ccf2c368 | |||
| 27a0010b6e | |||
| b06b9cc7a2 | |||
| 943f91cf0b | |||
| bd8caa5294 | |||
| 3ddf349ca1 | |||
| d707d241fc | |||
| aa5d2cf9d7 | |||
| decf41a3cb | |||
| d7dc4f2143 | |||
| fbd94e4237 | |||
| 48cc8f4c8c | |||
| e1721012d8 | |||
| 5af01bef9f | |||
| 4c65780f11 | |||
| a9c4c8aefd | |||
| a769505a45 |
Vendored
+5
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -332,9 +332,6 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
|
||||
void SetupDenormControl(const Profile& profile, const IR::Program& program, EmitContext& ctx,
|
||||
Id main_func) {
|
||||
const Info& info{program.info};
|
||||
if (profile.has_broken_fp16_float_controls && info.uses_fp16) {
|
||||
return;
|
||||
}
|
||||
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
|
||||
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
|
||||
} else if (info.uses_fp32_denorms_flush) {
|
||||
@@ -406,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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -18,7 +15,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
|
||||
if (index_offset > 0) {
|
||||
index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset));
|
||||
}
|
||||
return ctx.uses_explicit_workgroup_layout
|
||||
return ctx.profile.support_explicit_workgroup_layout
|
||||
? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)
|
||||
: ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index);
|
||||
}
|
||||
@@ -158,7 +155,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
|
||||
}
|
||||
|
||||
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
|
||||
if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id shift_id{ctx.Const(3U)};
|
||||
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
|
||||
const Id pointer{
|
||||
|
||||
@@ -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,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -31,7 +28,7 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
|
||||
} // Anonymous namespace
|
||||
|
||||
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
|
||||
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
|
||||
@@ -42,7 +39,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
|
||||
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
|
||||
@@ -53,7 +50,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
|
||||
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
|
||||
} else {
|
||||
@@ -63,7 +60,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
|
||||
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
|
||||
} else {
|
||||
@@ -73,7 +70,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
|
||||
return ctx.OpLoad(ctx.U32[1], pointer);
|
||||
} else {
|
||||
@@ -82,7 +79,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
|
||||
return ctx.OpLoad(ctx.U32[2], pointer);
|
||||
} else {
|
||||
@@ -97,7 +94,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
|
||||
return ctx.OpLoad(ctx.U32[4], pointer);
|
||||
}
|
||||
@@ -113,7 +110,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
|
||||
}
|
||||
|
||||
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
|
||||
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value));
|
||||
@@ -123,7 +120,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
|
||||
}
|
||||
|
||||
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
|
||||
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
|
||||
} else {
|
||||
@@ -133,7 +130,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
|
||||
|
||||
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
|
||||
Id pointer{};
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
|
||||
} else {
|
||||
const Id shift{ctx.Const(2U)};
|
||||
@@ -144,7 +141,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
|
||||
}
|
||||
|
||||
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
|
||||
ctx.OpStore(pointer, value);
|
||||
return;
|
||||
@@ -159,7 +156,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
|
||||
}
|
||||
|
||||
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
|
||||
if (ctx.uses_explicit_workgroup_layout) {
|
||||
if (ctx.profile.support_explicit_workgroup_layout) {
|
||||
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
|
||||
ctx.OpStore(pointer, value);
|
||||
return;
|
||||
|
||||
@@ -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) {
|
||||
@@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) {
|
||||
Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer,
|
||||
Id value_type, Id memory_type, spv::Scope scope) {
|
||||
const bool is_shared{scope == spv::Scope::Workgroup};
|
||||
const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout};
|
||||
const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout};
|
||||
const Id cas_func{CasFunction(ctx, operation, value_type)};
|
||||
const Id zero{ctx.u32_zero_value};
|
||||
const Id scope_id{ctx.Const(static_cast<u32>(scope))};
|
||||
@@ -620,11 +620,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
|
||||
|
||||
return std::make_tuple(variable, element_pointer, pointer);
|
||||
}};
|
||||
uses_explicit_workgroup_layout =
|
||||
profile.support_explicit_workgroup_layout &&
|
||||
(!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) &&
|
||||
(!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access);
|
||||
if (uses_explicit_workgroup_layout) {
|
||||
if (profile.support_explicit_workgroup_layout) {
|
||||
AddExtension("SPV_KHR_workgroup_memory_explicit_layout");
|
||||
AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
|
||||
if (program.info.uses_int8) {
|
||||
@@ -942,10 +938,6 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
||||
if (!info.uses_global_memory || !profile.support_int64) {
|
||||
return;
|
||||
}
|
||||
if (!profile.support_descriptor_aliasing) {
|
||||
DefineGlobalMemoryFunctionsU32Fallback(info);
|
||||
return;
|
||||
}
|
||||
using DefPtr = Id StorageDefinitions::*;
|
||||
const Id zero{u32_zero_value};
|
||||
const auto define_body{[&](DefPtr ssbo_member, Id addr, Id element_pointer, u32 shift,
|
||||
@@ -1023,111 +1015,6 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
|
||||
define(&StorageDefinitions::U32x4, storage_types.U32x4, U32[4], sizeof(u32[4]));
|
||||
}
|
||||
|
||||
void EmitContext::DefineGlobalMemoryFunctionsU32Fallback(const Info& info) {
|
||||
const Id zero{u32_zero_value};
|
||||
const auto define_body{[&](Id addr, u32 num_words, auto&& callback) {
|
||||
AddLabel();
|
||||
const size_t num_buffers{info.storage_buffers_descriptors.size()};
|
||||
for (size_t index = 0; index < num_buffers; ++index) {
|
||||
if (!info.nvn_buffer_used[index]) {
|
||||
continue;
|
||||
}
|
||||
const auto& ssbo{info.storage_buffers_descriptors[index]};
|
||||
const u32 addr_word{ssbo.cbuf_offset / 4};
|
||||
const u32 addr_lo_comp{addr_word % 4};
|
||||
const Id cbuf{cbufs[ssbo.cbuf_index].U32x4};
|
||||
const Id addr_vec_pointer{
|
||||
OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(addr_word / 4))};
|
||||
const Id addr_vec{OpLoad(U32[4], addr_vec_pointer)};
|
||||
const Id addr_lo{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp)};
|
||||
const Id addr_hi{OpCompositeExtract(U32[1], addr_vec, addr_lo_comp + 1U)};
|
||||
const Id unaligned_addr{
|
||||
OpBitcast(U64, OpCompositeConstruct(U32[2], addr_lo, addr_hi))};
|
||||
|
||||
const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
|
||||
const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
|
||||
|
||||
const u32 size_word{addr_word + 2};
|
||||
Id size_vec{addr_vec};
|
||||
if (size_word / 4 != addr_word / 4) {
|
||||
const Id size_vec_pointer{
|
||||
OpAccessChain(uniform_types.U32x4, cbuf, zero, Const(size_word / 4))};
|
||||
size_vec = OpLoad(U32[4], size_vec_pointer);
|
||||
}
|
||||
const Id ssbo_size{
|
||||
OpUConvert(U64, OpCompositeExtract(U32[1], size_vec, size_word % 4))};
|
||||
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
|
||||
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
|
||||
OpULessThan(U1, addr, ssbo_end))};
|
||||
const Id then_label{OpLabel()};
|
||||
const Id else_label{OpLabel()};
|
||||
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
|
||||
OpBranchConditional(cond, then_label, else_label);
|
||||
AddLabel(then_label);
|
||||
const Id ssbo_id{ssbos[index].U32};
|
||||
const Id ssbo_offset{OpUConvert(U32[1], OpISub(U64, addr, ssbo_addr))};
|
||||
const Id base_word{OpShiftRightLogical(U32[1], ssbo_offset, Const(2U))};
|
||||
std::array<Id, 4> word_pointers{};
|
||||
for (u32 word = 0; word < num_words; ++word) {
|
||||
const Id word_index{word == 0 ? base_word
|
||||
: OpIAdd(U32[1], base_word, Const(word))};
|
||||
word_pointers[word] =
|
||||
OpAccessChain(storage_types.U32.element, ssbo_id, zero, word_index);
|
||||
}
|
||||
callback(word_pointers);
|
||||
AddLabel(else_label);
|
||||
}
|
||||
}};
|
||||
const auto define_load{[&](Id type, u32 num_words) {
|
||||
const Id function_type{TypeFunction(type, U64)};
|
||||
const Id func_id{OpFunction(type, spv::FunctionControlMask::MaskNone, function_type)};
|
||||
const Id addr{OpFunctionParameter(U64)};
|
||||
define_body(addr, num_words, [&](const std::array<Id, 4>& pointers) {
|
||||
std::array<Id, 4> words{};
|
||||
for (u32 word = 0; word < num_words; ++word) {
|
||||
words[word] = OpLoad(U32[1], pointers[word]);
|
||||
}
|
||||
switch (num_words) {
|
||||
case 1:
|
||||
OpReturnValue(words[0]);
|
||||
break;
|
||||
case 2:
|
||||
OpReturnValue(OpCompositeConstruct(type, words[0], words[1]));
|
||||
break;
|
||||
default:
|
||||
OpReturnValue(
|
||||
OpCompositeConstruct(type, words[0], words[1], words[2], words[3]));
|
||||
break;
|
||||
}
|
||||
});
|
||||
OpReturnValue(ConstantNull(type));
|
||||
OpFunctionEnd();
|
||||
return func_id;
|
||||
}};
|
||||
const auto define_write{[&](Id type, u32 num_words) {
|
||||
const Id function_type{TypeFunction(void_id, U64, type)};
|
||||
const Id func_id{OpFunction(void_id, spv::FunctionControlMask::MaskNone, function_type)};
|
||||
const Id addr{OpFunctionParameter(U64)};
|
||||
const Id data{OpFunctionParameter(type)};
|
||||
define_body(addr, num_words, [&](const std::array<Id, 4>& pointers) {
|
||||
for (u32 word = 0; word < num_words; ++word) {
|
||||
const Id value{num_words == 1 ? data : OpCompositeExtract(U32[1], data, word)};
|
||||
OpStore(pointers[word], value);
|
||||
}
|
||||
OpReturn();
|
||||
});
|
||||
OpReturn();
|
||||
OpFunctionEnd();
|
||||
return func_id;
|
||||
}};
|
||||
load_global_func_u32 = define_load(U32[1], 1);
|
||||
load_global_func_u32x2 = define_load(U32[2], 2);
|
||||
load_global_func_u32x4 = define_load(U32[4], 4);
|
||||
write_global_func_u32 = define_write(U32[1], 1);
|
||||
write_global_func_u32x2 = define_write(U32[2], 2);
|
||||
write_global_func_u32x4 = define_write(U32[4], 4);
|
||||
}
|
||||
|
||||
void EmitContext::DefineRescalingInput(const Info& info) {
|
||||
if (!info.uses_rescaling_uniform) {
|
||||
return;
|
||||
@@ -1239,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 {
|
||||
@@ -1247,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',
|
||||
@@ -1309,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);
|
||||
@@ -1488,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);
|
||||
|
||||
@@ -42,6 +42,7 @@ struct TextureDefinition {
|
||||
Id image_type;
|
||||
u32 count;
|
||||
bool is_multisample;
|
||||
bool is_integer;
|
||||
};
|
||||
|
||||
struct TextureBufferDefinition {
|
||||
@@ -310,10 +311,6 @@ public:
|
||||
|
||||
Id local_memory{};
|
||||
|
||||
/// True when this shader's shared memory uses SPV_KHR_workgroup_memory_explicit_layout.
|
||||
/// False when the host lacks the extension or a width this shader accesses natively.
|
||||
bool uses_explicit_workgroup_layout{};
|
||||
|
||||
Id shared_memory_u8{};
|
||||
Id shared_memory_u16{};
|
||||
Id shared_memory_u32{};
|
||||
@@ -391,7 +388,6 @@ private:
|
||||
void DefineAttributeMemAccess(const Info& info);
|
||||
void DefineWriteStorageCasLoopFunction(const Info& info);
|
||||
void DefineGlobalMemoryFunctions(const Info& info);
|
||||
void DefineGlobalMemoryFunctionsU32Fallback(const Info& info);
|
||||
void DefineRescalingInput(const Info& info);
|
||||
void DefineRescalingInputPushConstant();
|
||||
void DefineRescalingInputUniformConstant();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ struct Profile {
|
||||
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{};
|
||||
@@ -31,8 +33,6 @@ struct Profile {
|
||||
bool support_fp32_signed_zero_nan_preserve{};
|
||||
bool support_fp64_signed_zero_nan_preserve{};
|
||||
bool support_explicit_workgroup_layout{};
|
||||
bool support_workgroup_layout_8bit_access{};
|
||||
bool support_workgroup_layout_16bit_access{};
|
||||
bool support_vote{};
|
||||
u32 supported_subgroup_stages{0x7F};
|
||||
bool support_viewport_index_layer_non_geometry{};
|
||||
@@ -40,7 +40,6 @@ struct Profile {
|
||||
bool support_typeless_image_loads{};
|
||||
bool support_demote_to_helper_invocation{};
|
||||
bool support_int64_atomics{};
|
||||
bool support_shared_int64_atomics{};
|
||||
bool support_derivative_control{};
|
||||
bool support_geometry_shader_passthrough{};
|
||||
bool support_native_ndc{};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,6 +52,10 @@ bool H264::IsInterlaced() {
|
||||
|
||||
std::span<const u8> H264::ComposeFrame() {
|
||||
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext));
|
||||
const auto& params = current_context.h264_parameter_set;
|
||||
SetFrameDimensions(static_cast<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);
|
||||
|
||||
@@ -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(), ¤t_context, sizeof(VP8PictureInfo));
|
||||
SetFrameDimensions(current_context.frame_width, current_context.frame_height);
|
||||
|
||||
const bool is_key_frame = current_context.key_frame == 1u;
|
||||
const auto bitstream_size = size_t(current_context.vld_buffer_size);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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{});
|
||||
|
||||
@@ -628,6 +628,7 @@ private:
|
||||
|
||||
s32 id;
|
||||
s32 nvdec_id{-1};
|
||||
bool has_decoded_frame{};
|
||||
u32 syncpoint;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||
#define END_PUSH_CONSTANTS };
|
||||
#define UNIFORM(n)
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_OUTPUT_IMAGE 2
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_IMAGE 1
|
||||
|
||||
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||
|
||||
@@ -26,8 +25,7 @@
|
||||
#define BEGIN_PUSH_CONSTANTS
|
||||
#define END_PUSH_CONSTANTS
|
||||
#define UNIFORM(n) layout (location = n) uniform
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_IMAGE 0
|
||||
|
||||
#endif
|
||||
@@ -43,10 +41,6 @@ UNIFORM(6) uint block_height;
|
||||
UNIFORM(7) uint block_height_mask;
|
||||
END_PUSH_CONSTANTS
|
||||
|
||||
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
|
||||
uint swizzle_table[];
|
||||
};
|
||||
|
||||
#if HAS_EXTENDED_TYPES
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
|
||||
@@ -71,9 +65,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI
|
||||
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
|
||||
|
||||
uint SwizzleTable(uint pos) {
|
||||
const uint t[8] = uint[](
|
||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||
);
|
||||
const uint i = pos >> 4;
|
||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||
return (h << 4) | (pos & 0xf);
|
||||
}
|
||||
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos = pos & SWIZZLE_MASK;
|
||||
return swizzle_table[pos.y * 64 + pos.x];
|
||||
return SwizzleTable(pos.y * 64 + pos.x);
|
||||
}
|
||||
|
||||
uvec4 ReadTexel(uint offset) {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||
#define END_PUSH_CONSTANTS };
|
||||
#define UNIFORM(n)
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_OUTPUT_IMAGE 2
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_IMAGE 1
|
||||
|
||||
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||
|
||||
@@ -26,8 +25,7 @@
|
||||
#define BEGIN_PUSH_CONSTANTS
|
||||
#define END_PUSH_CONSTANTS
|
||||
#define UNIFORM(n) layout (location = n) uniform
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_IMAGE 0
|
||||
|
||||
#endif
|
||||
@@ -45,10 +43,6 @@ UNIFORM(8) uint block_depth;
|
||||
UNIFORM(9) uint block_depth_mask;
|
||||
END_PUSH_CONSTANTS
|
||||
|
||||
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
|
||||
uint swizzle_table[];
|
||||
};
|
||||
|
||||
#if HAS_EXTENDED_TYPES
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
|
||||
@@ -73,9 +67,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI
|
||||
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
|
||||
|
||||
uint SwizzleTable(uint pos) {
|
||||
const uint t[8] = uint[](
|
||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||
);
|
||||
const uint i = pos >> 4;
|
||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||
return (h << 4) | (pos & 0xf);
|
||||
}
|
||||
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos = pos & SWIZZLE_MASK;
|
||||
return swizzle_table[pos.y * 64 + pos.x];
|
||||
return SwizzleTable(pos.y * 64 + pos.x);
|
||||
}
|
||||
|
||||
uvec4 ReadTexel(uint offset) {
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||
#define END_PUSH_CONSTANTS };
|
||||
#define UNIFORM(n)
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_OUTPUT_BUFFER 2
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_BUFFER 1
|
||||
#else
|
||||
#extension GL_NV_gpu_shader5 : enable
|
||||
#ifdef GL_NV_gpu_shader5
|
||||
@@ -23,7 +22,6 @@
|
||||
#define BEGIN_PUSH_CONSTANTS
|
||||
#define END_PUSH_CONSTANTS
|
||||
#define UNIFORM(n) layout(location = n) uniform
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_OUTPUT_BUFFER 0
|
||||
#endif
|
||||
@@ -66,13 +64,9 @@ END_PUSH_CONSTANTS
|
||||
#endif
|
||||
|
||||
// --- Buffers ---
|
||||
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
|
||||
uint swizzle_table[];
|
||||
};
|
||||
|
||||
#if HAS_EXTENDED_TYPES
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
|
||||
#endif
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
|
||||
@@ -96,10 +90,20 @@ const uint GOB_SIZE_Z_SHIFT = 0;
|
||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
|
||||
|
||||
uint SwizzleTable(uint pos) {
|
||||
const uint t[8] = uint[](
|
||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||
);
|
||||
const uint i = pos >> 4;
|
||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||
return (h << 4) | (pos & 0xf);
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos &= SWIZZLE_MASK;
|
||||
return swizzle_table[pos.y * 64u + pos.x];
|
||||
return SwizzleTable(pos.y * 64u + pos.x);
|
||||
}
|
||||
|
||||
uvec4 ReadTexel(uint offset) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -56,10 +59,8 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)
|
||||
copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)),
|
||||
convert_s8d24_program(MakeProgram(OPENGL_CONVERT_S8D24_COMP)),
|
||||
convert_ms_to_nonms_program(MakeProgram(CONVERT_MSAA_TO_NON_MSAA_COMP)),
|
||||
convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP)) {
|
||||
const auto swizzle_table = Tegra::Texture::MakeSwizzleTable();
|
||||
swizzle_table_buffer.Create();
|
||||
glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0);
|
||||
convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP))
|
||||
{
|
||||
}
|
||||
|
||||
UtilShaders::~UtilShaders() = default;
|
||||
@@ -116,13 +117,11 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map,
|
||||
void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
|
||||
std::span<const SwizzleParameters> swizzles) {
|
||||
static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
|
||||
static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 1;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
|
||||
|
||||
program_manager.BindComputeProgram(block_linear_unswizzle_2d_program.handle);
|
||||
glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
|
||||
|
||||
const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
|
||||
for (const SwizzleParameters& swizzle : swizzles) {
|
||||
@@ -153,14 +152,11 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
|
||||
void UtilShaders::BlockLinearUpload3D(Image& image, const StagingBufferMap& map,
|
||||
std::span<const SwizzleParameters> swizzles) {
|
||||
static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8};
|
||||
|
||||
static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 1;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
|
||||
|
||||
glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
|
||||
program_manager.BindComputeProgram(block_linear_unswizzle_3d_program.handle);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
|
||||
|
||||
const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
|
||||
for (const SwizzleParameters& swizzle : swizzles) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -45,9 +48,6 @@ public:
|
||||
|
||||
private:
|
||||
ProgramManager& program_manager;
|
||||
|
||||
OGLBuffer swizzle_table_buffer;
|
||||
|
||||
OGLProgram astc_decoder_program;
|
||||
OGLProgram block_linear_unswizzle_2d_program;
|
||||
OGLProgram block_linear_unswizzle_3d_program;
|
||||
|
||||
@@ -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"
|
||||
@@ -653,71 +654,8 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
|
||||
scheduler.Finish();
|
||||
}
|
||||
|
||||
constexpr u32 BL3D_BINDING_SWIZZLE_TABLE = 0;
|
||||
constexpr u32 BL3D_BINDING_INPUT_BUFFER = 1;
|
||||
constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 2;
|
||||
|
||||
constexpr std::array<VkDescriptorSetLayoutBinding, 3> BL3D_DESCRIPTOR_SET_BINDINGS{{
|
||||
{
|
||||
.binding = BL3D_BINDING_SWIZZLE_TABLE,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // swizzle_table[]
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = BL3D_BINDING_INPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = BL3D_BINDING_OUTPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr DescriptorBankInfo BL3D_BANK_INFO{
|
||||
.uniform_buffers = 0,
|
||||
.storage_buffers = 3,
|
||||
.texture_buffers = 0,
|
||||
.image_buffers = 0,
|
||||
.textures = 0,
|
||||
.images = 0,
|
||||
.score = 3,
|
||||
};
|
||||
|
||||
constexpr std::array<VkDescriptorUpdateTemplateEntry, 3>
|
||||
BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
|
||||
{
|
||||
.dstBinding = BL3D_BINDING_SWIZZLE_TABLE,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3D_BINDING_SWIZZLE_TABLE * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = BL3D_BINDING_INPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = BL3D_BINDING_OUTPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
}
|
||||
}};
|
||||
constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0;
|
||||
constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1;
|
||||
|
||||
struct alignas(16) BlockLinearUnswizzle3DPushConstants {
|
||||
u32 blocks_dim[3]; // Offset 0
|
||||
@@ -745,11 +683,50 @@ BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass(
|
||||
DescriptorPool& descriptor_pool_,
|
||||
StagingBufferPool& staging_buffer_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
||||
: ComputePass(
|
||||
device_, scheduler_, descriptor_pool_,
|
||||
BL3D_DESCRIPTOR_SET_BINDINGS,
|
||||
BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY,
|
||||
BL3D_BANK_INFO,
|
||||
: ComputePass(device_, scheduler_, descriptor_pool_,
|
||||
std::array<VkDescriptorSetLayoutBinding, 2>{{
|
||||
{
|
||||
.binding = BL3D_BINDING_INPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = BL3D_BINDING_OUTPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
}},
|
||||
std::array<VkDescriptorUpdateTemplateEntry, 2>{{
|
||||
{
|
||||
.dstBinding = BL3D_BINDING_INPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = BL3D_BINDING_OUTPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
}
|
||||
}},
|
||||
DescriptorBankInfo{
|
||||
.uniform_buffers = 0,
|
||||
.storage_buffers = 2,
|
||||
.texture_buffers = 0,
|
||||
.image_buffers = 0,
|
||||
.textures = 0,
|
||||
.images = 0,
|
||||
.score = 2,
|
||||
},
|
||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DPushConstants)>,
|
||||
BLOCK_LINEAR_UNSWIZZLE_3D_BCN_COMP_SPV),
|
||||
scheduler{scheduler_},
|
||||
@@ -769,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];
|
||||
@@ -822,8 +797,6 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
|
||||
pc.blocks_dim[2] = z_count; // Only process the count
|
||||
|
||||
compute_pass_descriptor_queue.Acquire(scheduler, 3);
|
||||
compute_pass_descriptor_queue.AddBuffer(*image.runtime->swizzle_table_buffer, 0,
|
||||
image.runtime->swizzle_table_size);
|
||||
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer,
|
||||
sw.buffer_offset + swizzled.offset,
|
||||
image.guest_size_bytes - sw.buffer_offset);
|
||||
|
||||
@@ -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) {
|
||||
@@ -397,7 +372,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.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(),
|
||||
@@ -416,10 +395,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.support_fp64_signed_zero_nan_preserve =
|
||||
float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE,
|
||||
.support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(),
|
||||
.support_workgroup_layout_8bit_access =
|
||||
device.IsWorkgroupMemoryExplicitLayout8BitSupported(),
|
||||
.support_workgroup_layout_16bit_access =
|
||||
device.IsWorkgroupMemoryExplicitLayout16BitSupported(),
|
||||
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
|
||||
.supported_subgroup_stages = supported_subgroup_stages,
|
||||
.support_viewport_index_layer_non_geometry =
|
||||
@@ -429,7 +404,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.support_demote_to_helper_invocation =
|
||||
device.IsExtShaderDemoteToHelperInvocationSupported(),
|
||||
.support_int64_atomics = device.IsExtShaderAtomicInt64Supported(),
|
||||
.support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(),
|
||||
.support_derivative_control = true,
|
||||
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
|
||||
.support_native_ndc = device.IsExtDepthClipControlSupported(),
|
||||
@@ -453,8 +427,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.has_broken_spirv_position_input = driver_id == false,
|
||||
.has_broken_unsigned_image_offsets = false,
|
||||
.has_broken_signed_operations = false,
|
||||
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
|
||||
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
|
||||
.ignore_nan_fp_comparisons = false,
|
||||
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
|
||||
.has_broken_robust =
|
||||
@@ -521,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();
|
||||
@@ -653,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;
|
||||
}
|
||||
|
||||
@@ -925,8 +903,12 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
}
|
||||
|
||||
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
const VkDriverIdKHR driver_id = device.GetDriverID();
|
||||
const bool needs_shared_mem_clamp =
|
||||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
|
||||
if (program.shared_memory_size > max_shared_memory) {
|
||||
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Compute shader 0x{:016x} requests {}KB shared memory but device max is {}KB - clamping",
|
||||
key.unique_hash,
|
||||
|
||||
@@ -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");
|
||||
@@ -1255,18 +1259,6 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_float_depth =
|
||||
regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT ||
|
||||
regs.zeta.format == Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT;
|
||||
if (is_float_depth && units != 0.0f && !device.IsExtDepthBiasControlSupported()) {
|
||||
static bool logged_float_bias_warning = false;
|
||||
if (!logged_float_bias_warning) {
|
||||
logged_float_bias_warning = true;
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Depth bias on a float depth target without VK_EXT_depth_bias_control");
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
|
||||
factor = regs.slope_scale_depth_bias, this](vk::CommandBuffer cmdbuf) {
|
||||
if (device.IsExtDepthBiasControlSupported()) {
|
||||
@@ -1735,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);
|
||||
});
|
||||
@@ -1786,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,
|
||||
@@ -909,40 +934,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
||||
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
|
||||
staging_buffer_pool, compute_pass_descriptor_queue);
|
||||
}
|
||||
|
||||
// --- Create swizzle table buffer ---
|
||||
{
|
||||
auto table = Tegra::Texture::MakeSwizzleTable();
|
||||
|
||||
swizzle_table_size = static_cast<VkDeviceSize>(table.size() * sizeof(table[0]));
|
||||
|
||||
auto staging = staging_buffer_pool.Request(swizzle_table_size, MemoryUsage::Upload);
|
||||
std::memcpy(staging.mapped_span.data(), table.data(), static_cast<size_t>(swizzle_table_size));
|
||||
|
||||
VkBufferCreateInfo ci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = swizzle_table_size,
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
swizzle_table_buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::DeviceLocal);
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([staging_buf = staging.buffer,
|
||||
dst_buf = *swizzle_table_buffer,
|
||||
size = swizzle_table_size,
|
||||
src_off = staging.offset](vk::CommandBuffer cmdbuf) {
|
||||
|
||||
const VkBufferCopy region{
|
||||
.srcOffset = src_off,
|
||||
.dstOffset = 0,
|
||||
.size = size,
|
||||
};
|
||||
cmdbuf.CopyBuffer(staging_buf, dst_buf, region);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::Finish() {
|
||||
@@ -1333,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),
|
||||
@@ -1589,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;
|
||||
@@ -1623,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1635,9 +1635,6 @@ Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBas
|
||||
Image::~Image() = default;
|
||||
|
||||
void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) {
|
||||
if (has_compute_unswizzle_buffer)
|
||||
return;
|
||||
|
||||
using VideoCore::Surface::BytesPerBlock;
|
||||
|
||||
const u32 block_bytes = BytesPerBlock(info.format); // 8 for BC1, 16 for BC6H
|
||||
@@ -1654,7 +1651,12 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) {
|
||||
static_cast<u64>(blocks_y) *
|
||||
static_cast<u64>(blocks_z);
|
||||
|
||||
compute_unswizzle_buffer_size = block_count * block_bytes;
|
||||
const VkDeviceSize required_size = block_count * block_bytes;
|
||||
if (has_compute_unswizzle_buffer && required_size <= compute_unswizzle_buffer_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
compute_unswizzle_buffer_size = required_size;
|
||||
|
||||
VkBufferCreateInfo ci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
@@ -1974,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);
|
||||
}
|
||||
@@ -2142,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;
|
||||
@@ -2276,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;
|
||||
@@ -2285,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;
|
||||
@@ -2295,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,
|
||||
@@ -2309,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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2331,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,
|
||||
@@ -2350,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(),
|
||||
@@ -2374,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2484,18 +2535,13 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
||||
|
||||
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
|
||||
if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
|
||||
ASSERT_MSG(false, "GPU unswizzle is disabled for BCn 3D texture");
|
||||
ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture");
|
||||
}
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bl3d_unswizzle_pass &&
|
||||
IsPixelFormatBCn(image.info.format) &&
|
||||
image.info.type == ImageType::e3D &&
|
||||
image.info.resources.levels == 1 &&
|
||||
image.info.resources.layers == 1) {
|
||||
|
||||
if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) {
|
||||
return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -130,9 +130,6 @@ public:
|
||||
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
||||
|
||||
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
||||
vk::Buffer swizzle_table_buffer;
|
||||
VkDeviceSize swizzle_table_size = 0;
|
||||
|
||||
std::optional<MSAACopyPass> msaa_copy_pass;
|
||||
const Settings::ResolutionScalingInfo& resolution;
|
||||
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||
@@ -373,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;
|
||||
@@ -388,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 {};
|
||||
@@ -408,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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1915,16 +1915,13 @@ void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
|
||||
ankerl::unordered_dense::set<SamplerId> active_sampler_ids;
|
||||
for (auto const& e : channel_state->sampler_ids)
|
||||
active_sampler_ids.insert(e.second);
|
||||
if constexpr (requires { runtime.Finish(); }) {
|
||||
runtime.Finish();
|
||||
}
|
||||
// Elements in the map must be necesarily valid
|
||||
size_t removed = 0;
|
||||
for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) {
|
||||
const SamplerId sampler_id = it->second;
|
||||
if (!sampler_id || sampler_id == CORRUPT_ID) {
|
||||
it = channel_state->samplers.erase(it);
|
||||
} else if (active_sampler_ids.contains(sampler_id)) {
|
||||
} else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) {
|
||||
++it;
|
||||
} else {
|
||||
slot_samplers.erase(sampler_id);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -23,24 +26,6 @@ constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_
|
||||
constexpr u32 SWIZZLE_X_BITS = 0b100101111;
|
||||
constexpr u32 SWIZZLE_Y_BITS = 0b011010000;
|
||||
|
||||
using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>;
|
||||
|
||||
/**
|
||||
* This table represents the internal swizzle of a gob, in format 16 bytes x 2 sector packing.
|
||||
* Calculates the offset of an (x, y) position within a swizzled texture.
|
||||
* Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188
|
||||
*/
|
||||
constexpr SwizzleTable MakeSwizzleTable() {
|
||||
SwizzleTable table{};
|
||||
for (u32 y = 0; y < table.size(); ++y) {
|
||||
for (u32 x = 0; x < table[0].size(); ++x) {
|
||||
table[y][x] = ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
|
||||
(y % 2) * 16 + (x % 16);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/// Unswizzles a block linear texture into linear memory.
|
||||
void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
|
||||
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
|
||||
|
||||
@@ -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,15 +504,25 @@ 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, "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;
|
||||
features.shader_atomic_int64.shaderSharedInt64Atomics = false;
|
||||
features.features.shaderInt64 = false;
|
||||
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
// BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load.
|
||||
@@ -565,8 +587,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
|
||||
if (!is_blit_depth24_stencil8_supported && !is_blit_depth32_stencil8_supported) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Neither shader export nor hardware blits available for "
|
||||
"depth/stencil.");
|
||||
"NVIDIA: Neither shader export nor hardware blits available for "
|
||||
"depth/stencil. Performance may be degraded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,13 +666,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
|
||||
if (is_turnip || is_qualcomm) {
|
||||
LOG_WARNING(Render_Vulkan, "Driver requires higher-than-reported binding limits");
|
||||
properties.properties.limits.maxVertexInputBindings =
|
||||
(std::max)(properties.properties.limits.maxVertexInputBindings, 32U);
|
||||
properties.properties.limits.maxVertexInputBindings = 32;
|
||||
}
|
||||
|
||||
const auto dyna_state = Settings::values.dyna_state.GetValue();
|
||||
switch (dyna_state) {
|
||||
case Settings::ExtendedDynamicState::Disabled:
|
||||
// Level 0: Disable all extended dynamic state extensions
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
|
||||
@@ -661,6 +683,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
dynamic_state3_enables = false;
|
||||
break;
|
||||
case Settings::ExtendedDynamicState::EDS1:
|
||||
// Level 1: Enable EDS1, disable EDS2 and EDS3
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
@@ -669,6 +692,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
dynamic_state3_enables = false;
|
||||
break;
|
||||
case Settings::ExtendedDynamicState::EDS2:
|
||||
// Level 2: Enable EDS1 + EDS2, disable EDS3
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = false;
|
||||
@@ -676,9 +700,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
break;
|
||||
case Settings::ExtendedDynamicState::EDS3:
|
||||
default:
|
||||
// Level 3: Enable all (EDS1 + EDS2 + EDS3)
|
||||
break;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -1148,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 =
|
||||
@@ -1157,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 &&
|
||||
@@ -1260,7 +1302,8 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_shader_atomic_int64
|
||||
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics;
|
||||
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
|
||||
features.shader_atomic_int64.shaderSharedInt64Atomics;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64,
|
||||
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
|
||||
|
||||
@@ -1309,7 +1352,10 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
|
||||
// VK_KHR_workgroup_memory_explicit_layout
|
||||
extensions.workgroup_memory_explicit_layout =
|
||||
features.features.shaderInt16 &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
|
||||
features.workgroup_memory_explicit_layout,
|
||||
@@ -1349,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) {
|
||||
|
||||
@@ -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,7 +430,6 @@ FN_MAX_LIMIT_LIST
|
||||
return properties.subgroup_properties.supportedOperations & feature;
|
||||
}
|
||||
|
||||
/// Returns the shader stages that support subgroup operations.
|
||||
VkShaderStageFlags GetSubgroupSupportedStages() const {
|
||||
return properties.subgroup_properties.supportedStages;
|
||||
}
|
||||
@@ -488,18 +514,6 @@ FN_MAX_LIMIT_LIST
|
||||
return extensions.workgroup_memory_explicit_layout;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports 8-bit accesses to workgroup explicit layout memory.
|
||||
bool IsWorkgroupMemoryExplicitLayout8BitSupported() const {
|
||||
return extensions.workgroup_memory_explicit_layout &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports 16-bit accesses to workgroup explicit layout memory.
|
||||
bool IsWorkgroupMemoryExplicitLayout16BitSupported() const {
|
||||
return extensions.workgroup_memory_explicit_layout && features.features.shaderInt16 &&
|
||||
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_image_format_list.
|
||||
bool IsKhrImageFormatListSupported() const {
|
||||
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
|
||||
@@ -526,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;
|
||||
}
|
||||
@@ -563,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;
|
||||
@@ -599,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;
|
||||
@@ -728,16 +761,9 @@ FN_MAX_LIMIT_LIST
|
||||
features.provoking_vertex.transformFeedbackPreservesProvokingVertex;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports int64 atomics on storage buffers.
|
||||
/// Returns true if the device supports VK_KHR_shader_atomic_int64.
|
||||
bool IsExtShaderAtomicInt64Supported() const {
|
||||
return extensions.shader_atomic_int64 &&
|
||||
features.shader_atomic_int64.shaderBufferInt64Atomics;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports int64 atomics on workgroup (shared) memory.
|
||||
bool IsSharedInt64AtomicsSupported() const {
|
||||
return extensions.shader_atomic_int64 &&
|
||||
features.shader_atomic_int64.shaderSharedInt64Atomics;
|
||||
return extensions.shader_atomic_int64;
|
||||
}
|
||||
|
||||
bool IsExtConditionalRendering() const {
|
||||
@@ -746,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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user