mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-31 18:46:08 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfb243159d |
@@ -440,7 +440,8 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
|
|||||||
}
|
}
|
||||||
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
|
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
|
||||||
info.uses_subgroup_shuffles) &&
|
info.uses_subgroup_shuffles) &&
|
||||||
profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) {
|
profile.support_vote &&
|
||||||
|
(ctx.stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage)) {
|
||||||
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
|
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
|
||||||
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
|
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
|
||||||
if (!profile.warp_size_potentially_larger_than_guest) {
|
if (!profile.warp_size_potentially_larger_than_guest) {
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ Id SubgroupScope(EmitContext& ctx) {
|
|||||||
return ctx.Const(static_cast<u32>(spv::Scope::Subgroup));
|
return ctx.Const(static_cast<u32>(spv::Scope::Subgroup));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some mobile GPUs (e.g. Adreno/Turnip) only advertise subgroup ballot/shuffle support for the
|
||||||
|
// fragment and compute stages (VkPhysicalDeviceSubgroupProperties::supportedStages), even though
|
||||||
|
// they support these operations elsewhere. Guest shaders that use VOTE/SHFL in a geometry program
|
||||||
|
// would otherwise emit GroupNonUniform* SPIR-V the driver never declared support for in that
|
||||||
|
// stage. There is no barrier in the geometry stage, so a real cross-invocation emulation can't be
|
||||||
|
// made correct; instead, treat the current invocation as if it were alone in its subgroup. This is
|
||||||
|
// semantically wrong for guest code that relies on genuine cross-lane communication, but it is
|
||||||
|
// well-defined, valid SPIR-V that doesn't depend on unsupported hardware capabilities.
|
||||||
|
bool NeedsGeometrySubgroupFallback(EmitContext& ctx) {
|
||||||
|
return ctx.stage == Stage::Geometry && !ctx.profile.support_subgroup_in_geometry_stage;
|
||||||
|
}
|
||||||
|
|
||||||
bool StageSupportsSubgroups(EmitContext& ctx) {
|
bool StageSupportsSubgroups(EmitContext& ctx) {
|
||||||
return ctx.profile.SupportsSubgroupStage(ctx.stage);
|
return ctx.profile.SupportsSubgroupStage(ctx.stage);
|
||||||
}
|
}
|
||||||
@@ -94,6 +106,9 @@ Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
|
|||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
Id EmitLaneId(EmitContext& ctx) {
|
Id EmitLaneId(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.u32_zero_value;
|
||||||
|
}
|
||||||
const Id id{GetThreadId(ctx)};
|
const Id id{GetThreadId(ctx)};
|
||||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||||
return id;
|
return id;
|
||||||
@@ -102,6 +117,9 @@ Id EmitLaneId(EmitContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitVoteAll(EmitContext& ctx, Id pred) {
|
Id EmitVoteAll(EmitContext& ctx, Id pred) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return pred;
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return pred;
|
return pred;
|
||||||
}
|
}
|
||||||
@@ -118,6 +136,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitVoteAny(EmitContext& ctx, Id pred) {
|
Id EmitVoteAny(EmitContext& ctx, Id pred) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return pred;
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return pred;
|
return pred;
|
||||||
}
|
}
|
||||||
@@ -134,6 +155,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
|
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.true_value;
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.true_value;
|
return ctx.true_value;
|
||||||
}
|
}
|
||||||
@@ -151,6 +175,16 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
|
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
// Reflect only this invocation's own predicate. There is no way to observe other
|
||||||
|
// invocations' predicates without real subgroup hardware support in this stage, so this
|
||||||
|
// is a best-effort approximation: it keeps any branch gated on "did anyone match" live
|
||||||
|
// (rather than letting the SPIR-V optimizer prove it dead, which previously caused
|
||||||
|
// indirect draws fed by this shader to see indexCount=instanceCount=0), but any downstream
|
||||||
|
// math that assumes a real cross-lane population count (e.g. popcount-based compaction
|
||||||
|
// offsets) will not be correct.
|
||||||
|
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1U), ctx.u32_zero_value);
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value);
|
return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value);
|
||||||
}
|
}
|
||||||
@@ -162,6 +196,9 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupEqMask(EmitContext& ctx) {
|
Id EmitSubgroupEqMask(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.Const(1U);
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.Const(1u);
|
return ctx.Const(1u);
|
||||||
}
|
}
|
||||||
@@ -169,6 +206,9 @@ Id EmitSubgroupEqMask(EmitContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupLtMask(EmitContext& ctx) {
|
Id EmitSubgroupLtMask(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.u32_zero_value;
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.u32_zero_value;
|
return ctx.u32_zero_value;
|
||||||
}
|
}
|
||||||
@@ -176,6 +216,9 @@ Id EmitSubgroupLtMask(EmitContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupLeMask(EmitContext& ctx) {
|
Id EmitSubgroupLeMask(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.Const(1U);
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.Const(1u);
|
return ctx.Const(1u);
|
||||||
}
|
}
|
||||||
@@ -183,6 +226,9 @@ Id EmitSubgroupLeMask(EmitContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupGtMask(EmitContext& ctx) {
|
Id EmitSubgroupGtMask(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.u32_zero_value;
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.u32_zero_value;
|
return ctx.u32_zero_value;
|
||||||
}
|
}
|
||||||
@@ -190,6 +236,9 @@ Id EmitSubgroupGtMask(EmitContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitSubgroupGeMask(EmitContext& ctx) {
|
Id EmitSubgroupGeMask(EmitContext& ctx) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
return ctx.Const(1U);
|
||||||
|
}
|
||||||
if (!StageSupportsSubgroups(ctx)) {
|
if (!StageSupportsSubgroups(ctx)) {
|
||||||
return ctx.Const(1u);
|
return ctx.Const(1u);
|
||||||
}
|
}
|
||||||
@@ -198,6 +247,10 @@ Id EmitSubgroupGeMask(EmitContext& ctx) {
|
|||||||
|
|
||||||
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||||
Id segmentation_mask) {
|
Id segmentation_mask) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
SetInBoundsFlag(inst, ctx.false_value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
|
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
|
||||||
const Id thread_id{EmitLaneId(ctx)};
|
const Id thread_id{EmitLaneId(ctx)};
|
||||||
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
|
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
|
||||||
@@ -217,6 +270,10 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
|
|||||||
|
|
||||||
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||||
Id segmentation_mask) {
|
Id segmentation_mask) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
SetInBoundsFlag(inst, ctx.false_value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
const Id thread_id{EmitLaneId(ctx)};
|
const Id thread_id{EmitLaneId(ctx)};
|
||||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||||
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
|
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
|
||||||
@@ -232,6 +289,10 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
|||||||
|
|
||||||
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||||
Id segmentation_mask) {
|
Id segmentation_mask) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
SetInBoundsFlag(inst, ctx.false_value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
const Id thread_id{EmitLaneId(ctx)};
|
const Id thread_id{EmitLaneId(ctx)};
|
||||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||||
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
|
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
|
||||||
@@ -247,6 +308,10 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
|
|||||||
|
|
||||||
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||||
Id segmentation_mask) {
|
Id segmentation_mask) {
|
||||||
|
if (NeedsGeometrySubgroupFallback(ctx)) {
|
||||||
|
SetInBoundsFlag(inst, ctx.false_value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
const Id thread_id{EmitLaneId(ctx)};
|
const Id thread_id{EmitLaneId(ctx)};
|
||||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||||
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
|
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
|
||||||
|
|||||||
@@ -1455,13 +1455,14 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
|||||||
if (info.uses_is_helper_invocation) {
|
if (info.uses_is_helper_invocation) {
|
||||||
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
|
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
|
||||||
}
|
}
|
||||||
if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) {
|
if (info.uses_subgroup_mask &&
|
||||||
|
(stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage)) {
|
||||||
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
|
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
|
||||||
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
|
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
|
||||||
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
|
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
|
||||||
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
|
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
|
||||||
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
|
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
|
||||||
if (stage == Stage::Fragment) {
|
if (profile.support_explicit_workgroup_layout) {
|
||||||
Decorate(subgroup_mask_eq, spv::Decoration::Flat);
|
Decorate(subgroup_mask_eq, spv::Decoration::Flat);
|
||||||
Decorate(subgroup_mask_lt, spv::Decoration::Flat);
|
Decorate(subgroup_mask_lt, spv::Decoration::Flat);
|
||||||
Decorate(subgroup_mask_le, spv::Decoration::Flat);
|
Decorate(subgroup_mask_le, spv::Decoration::Flat);
|
||||||
@@ -1469,10 +1470,11 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
|||||||
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
|
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
|
if (info.uses_fswzadd ||
|
||||||
(profile.warp_size_potentially_larger_than_guest &&
|
((info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
|
||||||
(info.uses_subgroup_vote || info.uses_subgroup_mask))) &&
|
(profile.warp_size_potentially_larger_than_guest &&
|
||||||
profile.SupportsSubgroupStage(stage)) {
|
(info.uses_subgroup_vote || info.uses_subgroup_mask))) &&
|
||||||
|
(stage != Stage::Geometry || profile.support_subgroup_in_geometry_stage))) {
|
||||||
AddCapability(spv::Capability::GroupNonUniform);
|
AddCapability(spv::Capability::GroupNonUniform);
|
||||||
subgroup_local_invocation_id =
|
subgroup_local_invocation_id =
|
||||||
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
|
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ struct Profile {
|
|||||||
bool support_quad_shuffles{};
|
bool support_quad_shuffles{};
|
||||||
bool support_vote{};
|
bool support_vote{};
|
||||||
u32 supported_subgroup_stages{0x7F};
|
u32 supported_subgroup_stages{0x7F};
|
||||||
|
bool support_subgroup_in_geometry_stage{}; ///< True when the device advertises subgroup
|
||||||
|
///< ballot/shuffle support for VK_SHADER_STAGE_GEOMETRY_BIT
|
||||||
|
///< (VkPhysicalDeviceSubgroupProperties::supportedStages).
|
||||||
|
///< Many mobile GPUs support subgroup ops only in
|
||||||
|
///< fragment/compute; guest shaders using VOTE/SHFL in a
|
||||||
|
///< geometry program need a non-subgroup fallback there.
|
||||||
bool support_viewport_index_layer_non_geometry{};
|
bool support_viewport_index_layer_non_geometry{};
|
||||||
bool support_viewport_mask{};
|
bool support_viewport_mask{};
|
||||||
bool support_typeless_image_loads{};
|
bool support_typeless_image_loads{};
|
||||||
|
|||||||
@@ -410,6 +410,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT),
|
.support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT),
|
||||||
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
|
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
|
||||||
.supported_subgroup_stages = supported_subgroup_stages,
|
.supported_subgroup_stages = supported_subgroup_stages,
|
||||||
|
.support_subgroup_in_geometry_stage =
|
||||||
|
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT) &&
|
||||||
|
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BALLOT_BIT) &&
|
||||||
|
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
|
||||||
|
device.IsSubgroupFeatureSupportedInStage(VK_SHADER_STAGE_GEOMETRY_BIT),
|
||||||
.support_viewport_index_layer_non_geometry =
|
.support_viewport_index_layer_non_geometry =
|
||||||
device.IsExtShaderViewportIndexLayerSupported(),
|
device.IsExtShaderViewportIndexLayerSupported(),
|
||||||
.support_viewport_mask = device.IsNvViewportArray2Supported(),
|
.support_viewport_mask = device.IsNvViewportArray2Supported(),
|
||||||
|
|||||||
@@ -479,6 +479,11 @@ FN_MAX_LIMIT_LIST
|
|||||||
return properties.subgroup_properties.supportedStages;
|
return properties.subgroup_properties.supportedStages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device supports subgroup ballot/shuffle in the given shader stage.
|
||||||
|
bool IsSubgroupFeatureSupportedInStage(VkShaderStageFlagBits stage) const {
|
||||||
|
return properties.subgroup_properties.supportedStages & stage;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the maximum number of push descriptors.
|
/// Returns the maximum number of push descriptors.
|
||||||
u32 MaxPushDescriptors() const {
|
u32 MaxPushDescriptors() const {
|
||||||
return properties.push_descriptor.maxPushDescriptors;
|
return properties.push_descriptor.maxPushDescriptors;
|
||||||
|
|||||||
Reference in New Issue
Block a user