diff --git a/renderdoc/driver/shaders/spirv/spirv_editor.cpp b/renderdoc/driver/shaders/spirv/spirv_editor.cpp index c3096aba1..d47d201a4 100644 --- a/renderdoc/driver/shaders/spirv/spirv_editor.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_editor.cpp @@ -23,10 +23,399 @@ ******************************************************************************/ #include "spirv_editor.h" +#include #include #include "common/common.h" #include "serialise/serialiser.h" +// hopefully this will be in the official spirv.hpp soon +// clang-format off +namespace spv +{ +inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { + *hasResult = *hasResultType = false; + switch (opcode) { + default: /* unknown opcode */ break; + case OpNop: *hasResult = false; *hasResultType = false; break; + case OpUndef: *hasResult = true; *hasResultType = true; break; + case OpSourceContinued: *hasResult = false; *hasResultType = false; break; + case OpSource: *hasResult = false; *hasResultType = false; break; + case OpSourceExtension: *hasResult = false; *hasResultType = false; break; + case OpName: *hasResult = false; *hasResultType = false; break; + case OpMemberName: *hasResult = false; *hasResultType = false; break; + case OpString: *hasResult = true; *hasResultType = false; break; + case OpLine: *hasResult = false; *hasResultType = false; break; + case OpExtension: *hasResult = false; *hasResultType = false; break; + case OpExtInstImport: *hasResult = true; *hasResultType = false; break; + case OpExtInst: *hasResult = true; *hasResultType = true; break; + case OpMemoryModel: *hasResult = false; *hasResultType = false; break; + case OpEntryPoint: *hasResult = false; *hasResultType = false; break; + case OpExecutionMode: *hasResult = false; *hasResultType = false; break; + case OpCapability: *hasResult = false; *hasResultType = false; break; + case OpTypeVoid: *hasResult = true; *hasResultType = false; break; + case OpTypeBool: *hasResult = true; *hasResultType = false; break; + case OpTypeInt: *hasResult = true; *hasResultType = false; break; + case OpTypeFloat: *hasResult = true; *hasResultType = false; break; + case OpTypeVector: *hasResult = true; *hasResultType = false; break; + case OpTypeMatrix: *hasResult = true; *hasResultType = false; break; + case OpTypeImage: *hasResult = true; *hasResultType = false; break; + case OpTypeSampler: *hasResult = true; *hasResultType = false; break; + case OpTypeSampledImage: *hasResult = true; *hasResultType = false; break; + case OpTypeArray: *hasResult = true; *hasResultType = false; break; + case OpTypeRuntimeArray: *hasResult = true; *hasResultType = false; break; + case OpTypeStruct: *hasResult = true; *hasResultType = false; break; + case OpTypeOpaque: *hasResult = true; *hasResultType = false; break; + case OpTypePointer: *hasResult = true; *hasResultType = false; break; + case OpTypeFunction: *hasResult = true; *hasResultType = false; break; + case OpTypeEvent: *hasResult = true; *hasResultType = false; break; + case OpTypeDeviceEvent: *hasResult = true; *hasResultType = false; break; + case OpTypeReserveId: *hasResult = true; *hasResultType = false; break; + case OpTypeQueue: *hasResult = true; *hasResultType = false; break; + case OpTypePipe: *hasResult = true; *hasResultType = false; break; + case OpTypeForwardPointer: *hasResult = false; *hasResultType = false; break; + case OpConstantTrue: *hasResult = true; *hasResultType = true; break; + case OpConstantFalse: *hasResult = true; *hasResultType = true; break; + case OpConstant: *hasResult = true; *hasResultType = true; break; + case OpConstantComposite: *hasResult = true; *hasResultType = true; break; + case OpConstantSampler: *hasResult = true; *hasResultType = true; break; + case OpConstantNull: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantTrue: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantFalse: *hasResult = true; *hasResultType = true; break; + case OpSpecConstant: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantComposite: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantOp: *hasResult = true; *hasResultType = true; break; + case OpFunction: *hasResult = true; *hasResultType = true; break; + case OpFunctionParameter: *hasResult = true; *hasResultType = true; break; + case OpFunctionEnd: *hasResult = false; *hasResultType = false; break; + case OpFunctionCall: *hasResult = true; *hasResultType = true; break; + case OpVariable: *hasResult = true; *hasResultType = true; break; + case OpImageTexelPointer: *hasResult = true; *hasResultType = true; break; + case OpLoad: *hasResult = true; *hasResultType = true; break; + case OpStore: *hasResult = false; *hasResultType = false; break; + case OpCopyMemory: *hasResult = false; *hasResultType = false; break; + case OpCopyMemorySized: *hasResult = false; *hasResultType = false; break; + case OpAccessChain: *hasResult = true; *hasResultType = true; break; + case OpInBoundsAccessChain: *hasResult = true; *hasResultType = true; break; + case OpPtrAccessChain: *hasResult = true; *hasResultType = true; break; + case OpArrayLength: *hasResult = true; *hasResultType = true; break; + case OpGenericPtrMemSemantics: *hasResult = true; *hasResultType = true; break; + case OpInBoundsPtrAccessChain: *hasResult = true; *hasResultType = true; break; + case OpDecorate: *hasResult = false; *hasResultType = false; break; + case OpMemberDecorate: *hasResult = false; *hasResultType = false; break; + case OpDecorationGroup: *hasResult = true; *hasResultType = false; break; + case OpGroupDecorate: *hasResult = false; *hasResultType = false; break; + case OpGroupMemberDecorate: *hasResult = false; *hasResultType = false; break; + case OpVectorExtractDynamic: *hasResult = true; *hasResultType = true; break; + case OpVectorInsertDynamic: *hasResult = true; *hasResultType = true; break; + case OpVectorShuffle: *hasResult = true; *hasResultType = true; break; + case OpCompositeConstruct: *hasResult = true; *hasResultType = true; break; + case OpCompositeExtract: *hasResult = true; *hasResultType = true; break; + case OpCompositeInsert: *hasResult = true; *hasResultType = true; break; + case OpCopyObject: *hasResult = true; *hasResultType = true; break; + case OpTranspose: *hasResult = true; *hasResultType = true; break; + case OpSampledImage: *hasResult = true; *hasResultType = true; break; + case OpImageSampleImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageFetch: *hasResult = true; *hasResultType = true; break; + case OpImageGather: *hasResult = true; *hasResultType = true; break; + case OpImageDrefGather: *hasResult = true; *hasResultType = true; break; + case OpImageRead: *hasResult = true; *hasResultType = true; break; + case OpImageWrite: *hasResult = false; *hasResultType = false; break; + case OpImage: *hasResult = true; *hasResultType = true; break; + case OpImageQueryFormat: *hasResult = true; *hasResultType = true; break; + case OpImageQueryOrder: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySizeLod: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySize: *hasResult = true; *hasResultType = true; break; + case OpImageQueryLod: *hasResult = true; *hasResultType = true; break; + case OpImageQueryLevels: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySamples: *hasResult = true; *hasResultType = true; break; + case OpConvertFToU: *hasResult = true; *hasResultType = true; break; + case OpConvertFToS: *hasResult = true; *hasResultType = true; break; + case OpConvertSToF: *hasResult = true; *hasResultType = true; break; + case OpConvertUToF: *hasResult = true; *hasResultType = true; break; + case OpUConvert: *hasResult = true; *hasResultType = true; break; + case OpSConvert: *hasResult = true; *hasResultType = true; break; + case OpFConvert: *hasResult = true; *hasResultType = true; break; + case OpQuantizeToF16: *hasResult = true; *hasResultType = true; break; + case OpConvertPtrToU: *hasResult = true; *hasResultType = true; break; + case OpSatConvertSToU: *hasResult = true; *hasResultType = true; break; + case OpSatConvertUToS: *hasResult = true; *hasResultType = true; break; + case OpConvertUToPtr: *hasResult = true; *hasResultType = true; break; + case OpPtrCastToGeneric: *hasResult = true; *hasResultType = true; break; + case OpGenericCastToPtr: *hasResult = true; *hasResultType = true; break; + case OpGenericCastToPtrExplicit: *hasResult = true; *hasResultType = true; break; + case OpBitcast: *hasResult = true; *hasResultType = true; break; + case OpSNegate: *hasResult = true; *hasResultType = true; break; + case OpFNegate: *hasResult = true; *hasResultType = true; break; + case OpIAdd: *hasResult = true; *hasResultType = true; break; + case OpFAdd: *hasResult = true; *hasResultType = true; break; + case OpISub: *hasResult = true; *hasResultType = true; break; + case OpFSub: *hasResult = true; *hasResultType = true; break; + case OpIMul: *hasResult = true; *hasResultType = true; break; + case OpFMul: *hasResult = true; *hasResultType = true; break; + case OpUDiv: *hasResult = true; *hasResultType = true; break; + case OpSDiv: *hasResult = true; *hasResultType = true; break; + case OpFDiv: *hasResult = true; *hasResultType = true; break; + case OpUMod: *hasResult = true; *hasResultType = true; break; + case OpSRem: *hasResult = true; *hasResultType = true; break; + case OpSMod: *hasResult = true; *hasResultType = true; break; + case OpFRem: *hasResult = true; *hasResultType = true; break; + case OpFMod: *hasResult = true; *hasResultType = true; break; + case OpVectorTimesScalar: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesScalar: *hasResult = true; *hasResultType = true; break; + case OpVectorTimesMatrix: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesVector: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesMatrix: *hasResult = true; *hasResultType = true; break; + case OpOuterProduct: *hasResult = true; *hasResultType = true; break; + case OpDot: *hasResult = true; *hasResultType = true; break; + case OpIAddCarry: *hasResult = true; *hasResultType = true; break; + case OpISubBorrow: *hasResult = true; *hasResultType = true; break; + case OpUMulExtended: *hasResult = true; *hasResultType = true; break; + case OpSMulExtended: *hasResult = true; *hasResultType = true; break; + case OpAny: *hasResult = true; *hasResultType = true; break; + case OpAll: *hasResult = true; *hasResultType = true; break; + case OpIsNan: *hasResult = true; *hasResultType = true; break; + case OpIsInf: *hasResult = true; *hasResultType = true; break; + case OpIsFinite: *hasResult = true; *hasResultType = true; break; + case OpIsNormal: *hasResult = true; *hasResultType = true; break; + case OpSignBitSet: *hasResult = true; *hasResultType = true; break; + case OpLessOrGreater: *hasResult = true; *hasResultType = true; break; + case OpOrdered: *hasResult = true; *hasResultType = true; break; + case OpUnordered: *hasResult = true; *hasResultType = true; break; + case OpLogicalEqual: *hasResult = true; *hasResultType = true; break; + case OpLogicalNotEqual: *hasResult = true; *hasResultType = true; break; + case OpLogicalOr: *hasResult = true; *hasResultType = true; break; + case OpLogicalAnd: *hasResult = true; *hasResultType = true; break; + case OpLogicalNot: *hasResult = true; *hasResultType = true; break; + case OpSelect: *hasResult = true; *hasResultType = true; break; + case OpIEqual: *hasResult = true; *hasResultType = true; break; + case OpINotEqual: *hasResult = true; *hasResultType = true; break; + case OpUGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpSGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpUGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpSGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpULessThan: *hasResult = true; *hasResultType = true; break; + case OpSLessThan: *hasResult = true; *hasResultType = true; break; + case OpULessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpSLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdNotEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordNotEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdLessThan: *hasResult = true; *hasResultType = true; break; + case OpFUnordLessThan: *hasResult = true; *hasResultType = true; break; + case OpFOrdGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpFUnordGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpFOrdLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpShiftRightLogical: *hasResult = true; *hasResultType = true; break; + case OpShiftRightArithmetic: *hasResult = true; *hasResultType = true; break; + case OpShiftLeftLogical: *hasResult = true; *hasResultType = true; break; + case OpBitwiseOr: *hasResult = true; *hasResultType = true; break; + case OpBitwiseXor: *hasResult = true; *hasResultType = true; break; + case OpBitwiseAnd: *hasResult = true; *hasResultType = true; break; + case OpNot: *hasResult = true; *hasResultType = true; break; + case OpBitFieldInsert: *hasResult = true; *hasResultType = true; break; + case OpBitFieldSExtract: *hasResult = true; *hasResultType = true; break; + case OpBitFieldUExtract: *hasResult = true; *hasResultType = true; break; + case OpBitReverse: *hasResult = true; *hasResultType = true; break; + case OpBitCount: *hasResult = true; *hasResultType = true; break; + case OpDPdx: *hasResult = true; *hasResultType = true; break; + case OpDPdy: *hasResult = true; *hasResultType = true; break; + case OpFwidth: *hasResult = true; *hasResultType = true; break; + case OpDPdxFine: *hasResult = true; *hasResultType = true; break; + case OpDPdyFine: *hasResult = true; *hasResultType = true; break; + case OpFwidthFine: *hasResult = true; *hasResultType = true; break; + case OpDPdxCoarse: *hasResult = true; *hasResultType = true; break; + case OpDPdyCoarse: *hasResult = true; *hasResultType = true; break; + case OpFwidthCoarse: *hasResult = true; *hasResultType = true; break; + case OpEmitVertex: *hasResult = false; *hasResultType = false; break; + case OpEndPrimitive: *hasResult = false; *hasResultType = false; break; + case OpEmitStreamVertex: *hasResult = false; *hasResultType = false; break; + case OpEndStreamPrimitive: *hasResult = false; *hasResultType = false; break; + case OpControlBarrier: *hasResult = false; *hasResultType = false; break; + case OpMemoryBarrier: *hasResult = false; *hasResultType = false; break; + case OpAtomicLoad: *hasResult = true; *hasResultType = true; break; + case OpAtomicStore: *hasResult = false; *hasResultType = false; break; + case OpAtomicExchange: *hasResult = true; *hasResultType = true; break; + case OpAtomicCompareExchange: *hasResult = true; *hasResultType = true; break; + case OpAtomicCompareExchangeWeak: *hasResult = true; *hasResultType = true; break; + case OpAtomicIIncrement: *hasResult = true; *hasResultType = true; break; + case OpAtomicIDecrement: *hasResult = true; *hasResultType = true; break; + case OpAtomicIAdd: *hasResult = true; *hasResultType = true; break; + case OpAtomicISub: *hasResult = true; *hasResultType = true; break; + case OpAtomicSMin: *hasResult = true; *hasResultType = true; break; + case OpAtomicUMin: *hasResult = true; *hasResultType = true; break; + case OpAtomicSMax: *hasResult = true; *hasResultType = true; break; + case OpAtomicUMax: *hasResult = true; *hasResultType = true; break; + case OpAtomicAnd: *hasResult = true; *hasResultType = true; break; + case OpAtomicOr: *hasResult = true; *hasResultType = true; break; + case OpAtomicXor: *hasResult = true; *hasResultType = true; break; + case OpPhi: *hasResult = true; *hasResultType = true; break; + case OpLoopMerge: *hasResult = false; *hasResultType = false; break; + case OpSelectionMerge: *hasResult = false; *hasResultType = false; break; + case OpLabel: *hasResult = true; *hasResultType = false; break; + case OpBranch: *hasResult = false; *hasResultType = false; break; + case OpBranchConditional: *hasResult = false; *hasResultType = false; break; + case OpSwitch: *hasResult = false; *hasResultType = false; break; + case OpKill: *hasResult = false; *hasResultType = false; break; + case OpReturn: *hasResult = false; *hasResultType = false; break; + case OpReturnValue: *hasResult = false; *hasResultType = false; break; + case OpUnreachable: *hasResult = false; *hasResultType = false; break; + case OpLifetimeStart: *hasResult = false; *hasResultType = false; break; + case OpLifetimeStop: *hasResult = false; *hasResultType = false; break; + case OpGroupAsyncCopy: *hasResult = true; *hasResultType = true; break; + case OpGroupWaitEvents: *hasResult = false; *hasResultType = false; break; + case OpGroupAll: *hasResult = true; *hasResultType = true; break; + case OpGroupAny: *hasResult = true; *hasResultType = true; break; + case OpGroupBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupIAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupFAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupFMin: *hasResult = true; *hasResultType = true; break; + case OpGroupUMin: *hasResult = true; *hasResultType = true; break; + case OpGroupSMin: *hasResult = true; *hasResultType = true; break; + case OpGroupFMax: *hasResult = true; *hasResultType = true; break; + case OpGroupUMax: *hasResult = true; *hasResultType = true; break; + case OpGroupSMax: *hasResult = true; *hasResultType = true; break; + case OpReadPipe: *hasResult = true; *hasResultType = true; break; + case OpWritePipe: *hasResult = true; *hasResultType = true; break; + case OpReservedReadPipe: *hasResult = true; *hasResultType = true; break; + case OpReservedWritePipe: *hasResult = true; *hasResultType = true; break; + case OpReserveReadPipePackets: *hasResult = true; *hasResultType = true; break; + case OpReserveWritePipePackets: *hasResult = true; *hasResultType = true; break; + case OpCommitReadPipe: *hasResult = false; *hasResultType = false; break; + case OpCommitWritePipe: *hasResult = false; *hasResultType = false; break; + case OpIsValidReserveId: *hasResult = true; *hasResultType = true; break; + case OpGetNumPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGetMaxPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupReserveReadPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupReserveWritePipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupCommitReadPipe: *hasResult = false; *hasResultType = false; break; + case OpGroupCommitWritePipe: *hasResult = false; *hasResultType = false; break; + case OpEnqueueMarker: *hasResult = true; *hasResultType = true; break; + case OpEnqueueKernel: *hasResult = true; *hasResultType = true; break; + case OpGetKernelNDrangeSubGroupCount: *hasResult = true; *hasResultType = true; break; + case OpGetKernelNDrangeMaxSubGroupSize: *hasResult = true; *hasResultType = true; break; + case OpGetKernelWorkGroupSize: *hasResult = true; *hasResultType = true; break; + case OpGetKernelPreferredWorkGroupSizeMultiple: *hasResult = true; *hasResultType = true; break; + case OpRetainEvent: *hasResult = false; *hasResultType = false; break; + case OpReleaseEvent: *hasResult = false; *hasResultType = false; break; + case OpCreateUserEvent: *hasResult = true; *hasResultType = true; break; + case OpIsValidEvent: *hasResult = true; *hasResultType = true; break; + case OpSetUserEventStatus: *hasResult = false; *hasResultType = false; break; + case OpCaptureEventProfilingInfo: *hasResult = false; *hasResultType = false; break; + case OpGetDefaultQueue: *hasResult = true; *hasResultType = true; break; + case OpBuildNDRange: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseFetch: *hasResult = true; *hasResultType = true; break; + case OpImageSparseGather: *hasResult = true; *hasResultType = true; break; + case OpImageSparseDrefGather: *hasResult = true; *hasResultType = true; break; + case OpImageSparseTexelsResident: *hasResult = true; *hasResultType = true; break; + case OpNoLine: *hasResult = false; *hasResultType = false; break; + case OpAtomicFlagTestAndSet: *hasResult = true; *hasResultType = true; break; + case OpAtomicFlagClear: *hasResult = false; *hasResultType = false; break; + case OpImageSparseRead: *hasResult = true; *hasResultType = true; break; + case OpSizeOf: *hasResult = true; *hasResultType = true; break; + case OpTypePipeStorage: *hasResult = true; *hasResultType = false; break; + case OpConstantPipeStorage: *hasResult = true; *hasResultType = true; break; + case OpCreatePipeFromPipeStorage: *hasResult = true; *hasResultType = true; break; + case OpGetKernelLocalSizeForSubgroupCount: *hasResult = true; *hasResultType = true; break; + case OpGetKernelMaxNumSubgroups: *hasResult = true; *hasResultType = true; break; + case OpTypeNamedBarrier: *hasResult = true; *hasResultType = false; break; + case OpNamedBarrierInitialize: *hasResult = true; *hasResultType = true; break; + case OpMemoryNamedBarrier: *hasResult = false; *hasResultType = false; break; + case OpModuleProcessed: *hasResult = false; *hasResultType = false; break; + case OpExecutionModeId: *hasResult = false; *hasResultType = false; break; + case OpDecorateId: *hasResult = false; *hasResultType = false; break; + case OpGroupNonUniformElect: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAll: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAny: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAllEqual: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBroadcastFirst: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallot: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformInverseBallot: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotBitExtract: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotBitCount: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotFindLSB: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotFindMSB: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffle: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleUp: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleDown: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformIAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformIMul: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMul: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformSMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformUMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformSMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformUMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseAnd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseOr: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalAnd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalOr: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformQuadBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformQuadSwap: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBallotKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupFirstInvocationKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAllKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAnyKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAllEqualKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupReadInvocationKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupIAddNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFAddNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupUMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupSMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupUMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupSMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpFragmentMaskFetchAMD: *hasResult = true; *hasResultType = true; break; + case OpFragmentFetchAMD: *hasResult = true; *hasResultType = true; break; + case OpWritePackedPrimitiveIndices4x8NV: *hasResult = false; *hasResultType = false; break; + case OpReportIntersectionNV: *hasResult = true; *hasResultType = true; break; + case OpIgnoreIntersectionNV: *hasResult = false; *hasResultType = false; break; + case OpTerminateRayNV: *hasResult = false; *hasResultType = false; break; + case OpTraceNV: *hasResult = false; *hasResultType = false; break; + case OpTypeAccelerationStructureNV: *hasResult = true; *hasResultType = false; break; + case OpExecuteCallableNV: *hasResult = false; *hasResultType = false; break; + case OpSubgroupShuffleINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleDownINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleUpINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleXorINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBlockReadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBlockWriteINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroupImageBlockReadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupImageBlockWriteINTEL: *hasResult = false; *hasResultType = false; break; + case OpDecorateStringGOOGLE: *hasResult = false; *hasResultType = false; break; + case OpMemberDecorateStringGOOGLE: *hasResult = false; *hasResultType = false; break; + case OpGroupNonUniformPartitionNV: *hasResult = true; *hasResultType = true; break; + case OpImageSampleFootprintNV: *hasResult = true; *hasResultType = true; break; + } +} +}; // namespace spv +// clang-format on + static const uint32_t FirstRealWord = 5; template <> @@ -106,6 +495,11 @@ SPIRVOperation SPIRVImage::decl(SPIRVEditor &editor) const arrayed, ms, sampled, (uint32_t)format}); } +SPIRVOperation SPIRVSampler::decl(SPIRVEditor &editor) const +{ + return SPIRVOperation(spv::OpTypeSampler, {0U}); +} + SPIRVOperation SPIRVSampledImage::decl(SPIRVEditor &editor) const { return SPIRVOperation(spv::OpTypeSampledImage, {0U, baseId}); @@ -135,6 +529,7 @@ SPIRVEditor::SPIRVEditor(std::vector &spirvWords) : spirv(spirvWords) moduleVersion.minor = uint8_t((spirv[1] & 0x0000ff00) >> 8); generator = spirv[2]; idOffsets.resize(spirv[3]); + idTypes.resize(spirv[3]); // [4] is reserved RDCASSERT(spirv[4] == 0); @@ -307,6 +702,7 @@ SPIRVId SPIRVEditor::MakeId() uint32_t ret = spirv[3]; spirv[3]++; idOffsets.resize(spirv[3]); + idTypes.resize(spirv[3]); return ret; } @@ -548,6 +944,17 @@ void SPIRVEditor::RegisterOp(SPIRVIterator it) { spv::Op opcode = it.opcode(); + { + bool hasResult = false, hasResultType = false; + spv::HasResultAndType(opcode, &hasResult, &hasResultType); + + if(hasResult && hasResultType) + { + RDCASSERT(it.word(2) < idTypes.size()); + idTypes[it.word(2)] = it.word(1); + } + } + if(opcode == spv::OpEntryPoint) { SPIRVEntry entry; @@ -583,6 +990,36 @@ void SPIRVEditor::RegisterOp(SPIRVIterator it) functions.push_back(id); } + else if(opcode == spv::OpVariable) + { + SPIRVVariable var; + var.type = it.word(1); + var.id = it.word(2); + var.storageClass = (spv::StorageClass)it.word(3); + if(it.size() > 4) + var.init = it.word(4); + + variables.push_back(var); + } + else if(opcode == spv::OpDecorate) + { + SPIRVDecoration decoration; + decoration.id = it.word(1); + decoration.dec = (spv::Decoration)it.word(2); + + RDCASSERTMSG("Too many parameters in decoration", it.size() <= 7, it.size()); + + for(size_t i = 0; i + 3 < it.size() && i < ARRAY_COUNT(decoration.parameters); i++) + decoration.parameters[i] = it.word(i + 3); + + auto it = std::lower_bound(decorations.begin(), decorations.end(), decoration); + decorations.insert(it, decoration); + + if(decoration.dec == spv::DecorationDescriptorSet) + bindings[decoration.id].set = decoration.parameters[0]; + if(decoration.dec == spv::DecorationBinding) + bindings[decoration.id].binding = decoration.parameters[0]; + } else if(opcode == spv::OpTypeVoid || opcode == spv::OpTypeBool || opcode == spv::OpTypeInt || opcode == spv::OpTypeFloat) { @@ -641,6 +1078,13 @@ void SPIRVEditor::RegisterOp(SPIRVIterator it) imageTypes[SPIRVImage(scalarIt, (spv::Dim)it.word(3), it.word(4), it.word(5), it.word(6), it.word(7), (spv::ImageFormat)it.word(8))] = id; } + else if(opcode == spv::OpTypeSampler) + { + SPIRVId id = it.word(1); + idOffsets[id] = it.offset; + + samplerTypes[SPIRVSampler()] = id; + } else if(opcode == spv::OpTypeSampledImage) { SPIRVId id = it.word(1); @@ -659,7 +1103,10 @@ void SPIRVEditor::RegisterOp(SPIRVIterator it) } else if(opcode == spv::OpTypeStruct) { - idOffsets[it.word(1)] = it.offset; + SPIRVId id = it.word(1); + idOffsets[id] = it.offset; + + structTypes.insert(id); } else if(opcode == spv::OpTypeFunction) { @@ -679,6 +1126,14 @@ void SPIRVEditor::UnregisterOp(SPIRVIterator it) { spv::Op opcode = it.opcode(); + { + bool hasResult = false, hasResultType = false; + spv::HasResultAndType(opcode, &hasResult, &hasResultType); + + if(hasResult && hasResultType) + idTypes[it.word(2)] = 0; + } + SPIRVId id; if(opcode == spv::OpEntryPoint) @@ -704,6 +1159,38 @@ void SPIRVEditor::UnregisterOp(SPIRVIterator it) } } } + else if(opcode == spv::OpVariable) + { + id = it.word(2); + for(auto varIt = variables.begin(); varIt != variables.end(); ++varIt) + { + if(varIt->id == id) + { + variables.erase(varIt); + break; + } + } + } + else if(opcode == spv::OpDecorate) + { + SPIRVDecoration decoration; + decoration.id = it.word(1); + decoration.dec = (spv::Decoration)it.word(2); + + RDCASSERTMSG("Too many parameters in decoration", it.size() <= 7, it.size()); + + for(size_t i = 0; i + 3 < it.size() && i < ARRAY_COUNT(decoration.parameters); i++) + decoration.parameters[i] = it.word(i + 3); + + auto it = std::find(decorations.begin(), decorations.end(), decoration); + if(it != decorations.end()) + decorations.erase(it); + + if(decoration.dec == spv::DecorationDescriptorSet) + bindings[decoration.id].set = SPIRVBinding().set; + if(decoration.dec == spv::DecorationBinding) + bindings[decoration.id].binding = SPIRVBinding().binding; + } else if(opcode == spv::OpCapability) { capabilities.erase((spv::Capability)it.word(1)); @@ -772,6 +1259,12 @@ void SPIRVEditor::UnregisterOp(SPIRVIterator it) imageTypes.erase(SPIRVImage(scalarIt, (spv::Dim)it.word(3), it.word(4), it.word(5), it.word(6), it.word(7), (spv::ImageFormat)it.word(8))); } + else if(opcode == spv::OpTypeSampler) + { + id = it.word(1); + + samplerTypes.erase(SPIRVSampler()); + } else if(opcode == spv::OpTypeSampledImage) { id = it.word(1); @@ -789,6 +1282,8 @@ void SPIRVEditor::UnregisterOp(SPIRVIterator it) else if(opcode == spv::OpTypeStruct) { id = it.word(1); + + structTypes.erase(id); } else if(opcode == spv::OpTypeFunction) { @@ -847,47 +1342,26 @@ void SPIRVEditor::addWords(size_t offs, int32_t num) o += num; } -template <> -std::map &SPIRVEditor::GetTable() -{ - return scalarTypes; -} +#define TYPETABLE(StructType, variable) \ + template <> \ + std::map &SPIRVEditor::GetTable() \ + { \ + return variable; \ + } \ + template <> \ + const std::map &SPIRVEditor::GetTable() const \ + { \ + return variable; \ + } -template <> -std::map &SPIRVEditor::GetTable() -{ - return vectorTypes; -} - -template <> -std::map &SPIRVEditor::GetTable() -{ - return matrixTypes; -} - -template <> -std::map &SPIRVEditor::GetTable() -{ - return pointerTypes; -} - -template <> -std::map &SPIRVEditor::GetTable() -{ - return imageTypes; -} - -template <> -std::map &SPIRVEditor::GetTable() -{ - return sampledImageTypes; -} - -template <> -std::map &SPIRVEditor::GetTable() -{ - return functionTypes; -} +TYPETABLE(SPIRVScalar, scalarTypes); +TYPETABLE(SPIRVVector, vectorTypes); +TYPETABLE(SPIRVMatrix, matrixTypes); +TYPETABLE(SPIRVPointer, pointerTypes); +TYPETABLE(SPIRVImage, imageTypes); +TYPETABLE(SPIRVSampler, samplerTypes); +TYPETABLE(SPIRVSampledImage, sampledImageTypes); +TYPETABLE(SPIRVFunction, functionTypes); #if ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/driver/shaders/spirv/spirv_editor.h b/renderdoc/driver/shaders/spirv/spirv_editor.h index 7e53dc0b7..37d92466c 100644 --- a/renderdoc/driver/shaders/spirv/spirv_editor.h +++ b/renderdoc/driver/shaders/spirv/spirv_editor.h @@ -31,6 +31,7 @@ #include #include "3rdparty/glslang/SPIRV/spirv.hpp" #include "api/replay/renderdoc_replay.h" +#include "common/common.h" class SPIRVOperation; class SPIRVEditor; @@ -167,8 +168,79 @@ struct SPIRVEntry std::string name; }; +struct SPIRVVariable +{ + SPIRVId id; + SPIRVId type; + spv::StorageClass storageClass; + SPIRVId init; + + bool operator<(const SPIRVVariable &o) const + { + if(id != o.id) + return id < o.id; + if(type != o.type) + return type < o.type; + if(storageClass != o.storageClass) + return storageClass < o.storageClass; + return init < o.init; + } + + bool operator!=(const SPIRVVariable &o) const { return !operator==(o); } + bool operator==(const SPIRVVariable &o) const + { + return id == o.id && type == o.type && storageClass == o.storageClass && init == o.init; + } +}; + +struct SPIRVDecoration +{ + SPIRVId id; + spv::Decoration dec = spv::DecorationMax; + uint32_t parameters[4] = {}; + + bool operator<(const SPIRVDecoration &o) const + { + if(id != o.id) + return id < o.id; + if(dec != o.dec) + return dec < o.dec; + + for(size_t i = 0; i < ARRAY_COUNT(parameters); i++) + if(parameters[i] != o.parameters[i]) + return parameters[i] < o.parameters[i]; + + return false; + } + + bool operator!=(const SPIRVDecoration &o) const { return !operator==(o); } + bool operator==(const SPIRVDecoration &o) const + { + return id == o.id && dec == o.dec && !memcmp(parameters, o.parameters, sizeof(parameters)); + } +}; + +struct SPIRVBinding +{ + SPIRVBinding() = default; + SPIRVBinding(uint32_t s, uint32_t b) : set(s), binding(b) {} + uint32_t set = 0; + uint32_t binding = ~0U; + + bool operator<(const SPIRVBinding &o) const + { + if(set != o.set) + return set < o.set; + return binding < o.binding; + } + + bool operator!=(const SPIRVBinding &o) const { return !operator==(o); } + bool operator==(const SPIRVBinding &o) const { return set == o.set && binding == o.binding; } +}; + struct SPIRVScalar { + SPIRVScalar() : type(spv::OpMax), width(0), signedness(false) {} constexpr SPIRVScalar(spv::Op t, uint32_t w, bool s) : type(t), width(w), signedness(s) {} SPIRVScalar(SPIRVIterator op); @@ -219,10 +291,14 @@ inline constexpr SPIRVScalar scalar(); SCALAR_TYPE(void, spv::OpTypeVoid, 0, false); SCALAR_TYPE(bool, spv::OpTypeBool, 0, false); +SCALAR_TYPE(uint8_t, spv::OpTypeInt, 8, false); SCALAR_TYPE(uint16_t, spv::OpTypeInt, 16, false); SCALAR_TYPE(uint32_t, spv::OpTypeInt, 32, false); +SCALAR_TYPE(uint64_t, spv::OpTypeInt, 64, false); +SCALAR_TYPE(int8_t, spv::OpTypeInt, 8, true); SCALAR_TYPE(int16_t, spv::OpTypeInt, 16, true); SCALAR_TYPE(int32_t, spv::OpTypeInt, 32, true); +SCALAR_TYPE(int64_t, spv::OpTypeInt, 64, true); SCALAR_TYPE(float, spv::OpTypeFloat, 32, false); SCALAR_TYPE(double, spv::OpTypeFloat, 64, false); @@ -324,6 +400,15 @@ struct SPIRVImage SPIRVOperation decl(SPIRVEditor &editor) const; }; +struct SPIRVSampler +{ + // no properties, all sampler types are equal + bool operator<(const SPIRVSampler &o) const { return false; } + bool operator!=(const SPIRVSampler &o) const { return false; } + bool operator==(const SPIRVSampler &o) const { return true; } + SPIRVOperation decl(SPIRVEditor &editor) const; +}; + struct SPIRVSampledImage { SPIRVSampledImage(SPIRVId b) : baseId(b) {} @@ -356,6 +441,12 @@ struct SPIRVFunction SPIRVOperation decl(SPIRVEditor &editor) const; }; +template +using SPIRVTypeId = std::pair; + +template +using SPIRVTypeIds = std::vector>; + // hack around enum class being useless for array indices :( struct SPIRVSection { @@ -462,6 +553,33 @@ public: return SPIRVId(); } + template + SPIRVTypeIds GetTypes() + { + std::map &table = GetTable(); + + SPIRVTypeIds ret; + + for(auto it = table.begin(); it != table.end(); ++it) + ret.push_back(*it); + + return ret; + } + + template + const std::map &GetTypeInfo() const + { + return GetTable(); + } + + SPIRVBinding GetBinding(SPIRVId id) const + { + auto it = bindings.find(id); + if(it == bindings.end()) + return SPIRVBinding(); + return it->second; + } + const std::set &GetStructTypes() const { return structTypes; } SPIRVId DeclareStructType(std::vector members); // helper for AddConstant @@ -490,7 +608,9 @@ public: // accessors to structs/vectors of data const std::vector &GetEntries() { return entries; } + const std::vector &GetVariables() { return variables; } const std::vector &GetFunctions() { return functions; } + SPIRVId GetIDType(SPIRVId id) { return idTypes[id]; } private: inline void addWords(size_t offs, size_t num) { addWords(offs, (int32_t)num); } void addWords(size_t offs, int32_t num); @@ -509,9 +629,15 @@ private: spv::AddressingModel addressmodel; spv::MemoryModel memorymodel; + std::vector decorations; + + std::map bindings; + std::vector idOffsets; + std::vector idTypes; std::vector entries; + std::vector variables; std::vector functions; std::set extensions; std::set capabilities; @@ -523,11 +649,17 @@ private: std::map matrixTypes; std::map pointerTypes; std::map imageTypes; + std::map samplerTypes; std::map sampledImageTypes; std::map functionTypes; + std::set structTypes; + template std::map &GetTable(); + template + const std::map &GetTable() const; + std::vector &spirv; }; \ No newline at end of file diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index 6af076ffa..aae0f2f76 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -8,6 +8,7 @@ set(sources vk_debug.h vk_debug.cpp vk_postvs.cpp + vk_bindless_feedback.cpp vk_overlay.cpp vk_msaa_array_conv.cpp vk_outputwindow.cpp diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj index 4cdddbaa6..82c5fd0ab 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj @@ -105,6 +105,7 @@ true + diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters index c41ce3b99..8ef091dc6 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters @@ -136,6 +136,9 @@ Util + + Replay + diff --git a/renderdoc/driver/vulkan/vk_bindless_feedback.cpp b/renderdoc/driver/vulkan/vk_bindless_feedback.cpp new file mode 100644 index 000000000..347cd0aa2 --- /dev/null +++ b/renderdoc/driver/vulkan/vk_bindless_feedback.cpp @@ -0,0 +1,875 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include "3rdparty/glslang/SPIRV/GLSL.std.450.h" +#include "3rdparty/glslang/SPIRV/spirv.hpp" +#include "driver/shaders/spirv/spirv_common.h" +#include "driver/shaders/spirv/spirv_editor.h" +#include "vk_core.h" +#include "vk_debug.h" +#include "vk_shader_cache.h" + +struct feedbackData +{ + uint64_t offset; + uint32_t numEntries; +}; + +void AnnotateShader(const SPIRVPatchData &patchData, const char *entryName, + const std::map &offsetMap, VkDeviceAddress addr, + std::vector &modSpirv) +{ + SPIRVEditor editor(modSpirv); + + const bool useBufferAddress = (addr != 0); + + SPIRVId uint32ID = editor.DeclareType(scalar()); + SPIRVId int32ID = editor.DeclareType(scalar()); + SPIRVId uint64ID, int64ID; + SPIRVId uint32StructID; + SPIRVId funcParamType; + + if(useBufferAddress) + { + // declare the int64 types we'll need + uint64ID = editor.DeclareType(scalar()); + int64ID = editor.DeclareType(scalar()); + + uint32StructID = editor.AddType(SPIRVOperation(spv::OpTypeStruct, {editor.MakeId(), uint32ID})); + + // any function parameters we add are uint64 byte offsets + funcParamType = uint64ID; + } + else + { + SPIRVId runtimeArrayID = + editor.AddType(SPIRVOperation(spv::OpTypeRuntimeArray, {editor.MakeId(), uint32ID})); + + editor.AddDecoration(SPIRVOperation( + spv::OpDecorate, {runtimeArrayID, spv::DecorationArrayStride, sizeof(uint32_t)})); + + uint32StructID = + editor.AddType(SPIRVOperation(spv::OpTypeStruct, {editor.MakeId(), runtimeArrayID})); + + // any function parameters we add are uint32 indices + funcParamType = uint32ID; + } + + editor.SetName(uint32StructID, "__rd_feedbackStruct"); + + editor.AddDecoration( + SPIRVOperation(spv::OpMemberDecorate, {uint32StructID, 0, spv::DecorationOffset, 0})); + + // map from variable ID to watch, to variable ID to get offset from (as a SPIR-V constant, + // or as either uint64 byte offset for buffer addressing or uint32 ssbo index otherwise) + std::map varLookup; + + // iterate over all variables. We do this here because in the absence of the buffer address + // extension we might declare our own below and patch bindings - so we need to look these up now + for(const SPIRVVariable &var : editor.GetVariables()) + { + // skip variables without one of these storage classes, as they are not descriptors + if(var.storageClass != spv::StorageClassUniformConstant && + var.storageClass != spv::StorageClassUniform && + var.storageClass != spv::StorageClassStorageBuffer) + continue; + + // get this variable's binding info + SPIRVBinding bind = editor.GetBinding(var.id); + + // if this is one of the bindings we care about + auto it = offsetMap.find(bind); + if(it != offsetMap.end()) + { + // store the offset for this variable so we watch for access chains and know where to store to + if(useBufferAddress) + { + SPIRVId id = varLookup[var.id] = editor.AddConstantImmediate(it->second.offset); + + editor.SetName( + id, StringFormat::Fmt("__feedbackOffset_set%u_bind%u", it->first.set, it->first.binding) + .c_str()); + } + else + { + // check that the offset fits in 32-bit word, convert byte offset to uint32 index + uint64_t index = it->second.offset / 4; + RDCASSERT(index < 0xFFFFFFFFULL, bind.set, bind.binding, it->second.offset); + SPIRVId id = varLookup[var.id] = editor.AddConstantImmediate(uint32_t(index)); + + editor.SetName( + id, StringFormat::Fmt("__feedbackIndex_set%u_bind%u", it->first.set, it->first.binding) + .c_str()); + } + } + } + + SPIRVId bufferAddressConst, ssboVar, uint32ptrtype; + + if(useBufferAddress) + { + // add the extension + editor.AddExtension("SPV_EXT_physical_storage_buffer"); + + // change the memory model to physical storage buffer 64 + SPIRVOperation op(editor.Begin(SPIRVSection::MemoryModel)); + op[1] = spv::AddressingModelPhysicalStorageBuffer64EXT; + + // add capabilities + editor.AddCapability(spv::CapabilityPhysicalStorageBufferAddressesEXT); + editor.AddCapability(spv::CapabilityInt64); + + // declare the address constants and make our pointers physical storage buffer pointers + bufferAddressConst = editor.AddConstantImmediate(addr); + uint32ptrtype = + editor.DeclareType(SPIRVPointer(uint32ID, spv::StorageClassPhysicalStorageBufferEXT)); + + editor.SetName(bufferAddressConst, "__rd_feedbackAddress"); + + // struct is block decorated + editor.AddDecoration(SPIRVOperation(spv::OpDecorate, {uint32StructID, spv::DecorationBlock})); + } + else + { + // the pointers are uniform pointers + SPIRVId bufptrtype = editor.DeclareType(SPIRVPointer(uint32StructID, spv::StorageClassUniform)); + uint32ptrtype = editor.DeclareType(SPIRVPointer(uint32ID, spv::StorageClassUniform)); + + // patch all bindings up by 1 + for(SPIRVIterator it = editor.Begin(SPIRVSection::Annotations), + end = editor.End(SPIRVSection::Annotations); + it < end; ++it) + { + // we will use descriptor set 0 for our own purposes if we don't have a buffer address. + // + // Since bindings are arbitrary, we just increase all user bindings to make room, and we'll + // redeclare the descriptor set layouts and pipeline layout. This is inevitable in the case + // where all descriptor sets are already used. In theory we only have to do this with set 0, + // but that requires knowing which variables are in set 0 and it's simpler to increase all + // bindings. + if(it.opcode() == spv::OpDecorate && it.word(2) == spv::DecorationBinding) + { + RDCASSERT(it.word(3) != 0xffffffff); + it.word(3) += 1; + } + } + + // add our SSBO variable, at set 0 binding 0 + ssboVar = editor.MakeId(); + editor.AddVariable( + SPIRVOperation(spv::OpVariable, {bufptrtype, ssboVar, spv::StorageClassUniform})); + editor.AddDecoration( + SPIRVOperation(spv::OpDecorate, {ssboVar, (uint32_t)spv::DecorationDescriptorSet, 0})); + editor.AddDecoration( + SPIRVOperation(spv::OpDecorate, {ssboVar, (uint32_t)spv::DecorationBinding, 0})); + + editor.SetName(ssboVar, "__rd_feedbackBuffer"); + + // struct is bufferblock decorated + editor.AddDecoration( + SPIRVOperation(spv::OpDecorate, {uint32StructID, (uint32_t)spv::DecorationBufferBlock})); + } + + SPIRVId rtarrayOffset = editor.AddConstantImmediate(0U); + SPIRVId usedValue = editor.AddConstantImmediate(0xFFFFFFFFU); + SPIRVId scope = editor.AddConstantImmediate(spv::ScopeInvocation); + SPIRVId semantics = editor.AddConstantImmediate(0U); + SPIRVId uint32shift = editor.AddConstantImmediate(2U); + + std::map intTypeLookup; + + for(auto scalarType : editor.GetTypeInfo()) + if(scalarType.first.type == spv::OpTypeInt) + intTypeLookup[scalarType.second] = scalarType.first; + + SPIRVId entryID; + for(const SPIRVEntry &entry : editor.GetEntries()) + { + if(entry.name == entryName) + { + entryID = entry.id; + break; + } + } + + SPIRVTypeIds funcTypes = editor.GetTypes(); + + // functions that have been patched with annotation & extra function parameters if needed + std::set patchedFunctions; + + // functions we need to patch, with the indices of which parameters have bindings coming along + // with + std::map> functionPatchQueue; + + // start with the entry point, with no parameters to patch + functionPatchQueue[entryID] = {}; + + // now keep patching functions until we have no more to patch + while(!functionPatchQueue.empty()) + { + SPIRVId funcId; + std::vector patchArgIndices; + + { + auto it = functionPatchQueue.begin(); + funcId = functionPatchQueue.begin()->first; + patchArgIndices = functionPatchQueue.begin()->second; + functionPatchQueue.erase(it); + + patchedFunctions.insert(funcId); + } + + SPIRVIterator it = editor.GetID(funcId); + + RDCASSERT(it.opcode() == spv::OpFunction); + + if(!patchArgIndices.empty()) + { + // find the function's type declaration, add the necessary arguments, redeclare and patch it + for(const SPIRVTypeId &funcType : funcTypes) + { + if(funcType.second == it.word(4)) + { + SPIRVFunction patchedFuncType = funcType.first; + for(size_t i = 0; i < patchArgIndices.size(); i++) + patchedFuncType.argumentIds.push_back(funcParamType); + + SPIRVId newFuncTypeID = editor.DeclareType(patchedFuncType); + + // re-fetch the iterator as it might have moved with the type declaration + it = editor.GetID(funcId); + + // change the declared function type + it.word(4) = newFuncTypeID; + + break; + } + } + } + + ++it; + + // onto the OpFunctionParameters. First allocate IDs for all our new function parameters + std::vector patchedParamIDs; + for(size_t i = 0; i < patchArgIndices.size(); i++) + patchedParamIDs.push_back(editor.MakeId()); + + size_t argIndex = 0; + size_t watchIndex = 0; + while(it.opcode() == spv::OpFunctionParameter) + { + // if this is a parameter we're patching, add it into varLookup + if(watchIndex < patchArgIndices.size() && patchArgIndices[watchIndex] == argIndex) + { + // when we see use of this parameter, patch it using the added parameter + varLookup[it.word(2)] = patchedParamIDs[watchIndex]; + // watch for the next argument + watchIndex++; + } + + argIndex++; + ++it; + } + + // we're past the existing function parameters, now declare our new ones + for(size_t i = 0; i < patchedParamIDs.size(); i++) + { + editor.AddOperation( + it, SPIRVOperation(spv::OpFunctionParameter, {funcParamType, patchedParamIDs[i]})); + ++it; + } + + // now patch accesses in the function body + for(; it; ++it) + { + // finish when we hit the end of the function + if(it.opcode() == spv::OpFunctionEnd) + break; + + // if we see an OpCopyObject, just add it to the map pointing to the same value + if(it.opcode() == spv::OpCopyObject) + { + SPIRVId sourcevar = it.word(3); + + // is this a var we want to snoop? + auto varIt = varLookup.find(sourcevar); + if(varIt != varLookup.end()) + { + varLookup[it.word(2)] = varIt->second; + } + } + + if(it.opcode() == spv::OpFunctionCall) + { + // check if any of the variables being passed are ones we care about. Accumulate the added + // parameters + std::vector funccall; + std::vector patchArgs; + + // examine each argument to see if it's one we care about + for(size_t i = 4; i < it.size(); i++) + { + // if this param we're snooping then pass our offset - whether it's a constant or a + // function + // argument itself - into the function call + auto varIt = varLookup.find(it.word(i)); + if(varIt != varLookup.end()) + { + funccall.push_back(varIt->second); + patchArgs.push_back(i - 4); + } + } + + if(!funccall.empty()) + { + // prepend all the existing words + for(size_t i = 1; i < it.size(); i++) + funccall.insert(funccall.begin() + i - 1, it.word(i)); + + SPIRVIterator oldCall = it; + + // add our patched call afterwards + it++; + editor.AddOperation(it, SPIRVOperation(spv::OpFunctionCall, funccall)); + + // remove the old call + editor.Remove(oldCall); + + // if this function isn't marked for patching yet, and isn't patched, queue it + if(functionPatchQueue[it.word(3)].empty() && + patchedFunctions.find(it.word(3)) == patchedFunctions.end()) + functionPatchQueue[it.word(3)] = patchArgs; + } + } + + // if we see an access chain of a variable we're snooping, save out the result + if(it.opcode() == spv::OpAccessChain || it.opcode() == spv::OpInBoundsAccessChain) + { + SPIRVId sourcevar = it.word(3); + + // is this a var we want to snoop? + auto varIt = varLookup.find(sourcevar); + if(varIt != varLookup.end()) + { + // multi-dimensional arrays of descriptors is not allowed - however an access chain could + // be longer than 5 words (1 index). Think of the case of a uniform buffer where the first + // index goes into the descriptor array, and further indices go inside the uniform buffer + // members. + RDCASSERT(it.size() >= 5, it.size()); + + SPIRVId index = it.word(4); + + // patch after the access chain + it++; + + // upcast the index to uint32 or uint64 depending on which path we're taking + uint32_t targetIndexWidth = useBufferAddress ? 64 : 32; + { + SPIRVId indexType = editor.GetIDType(index); + + if(indexType == SPIRVId()) + { + RDCERR("Unknown type for ID %u, defaulting to uint32_t", index); + indexType = uint32ID; + } + + SPIRVScalar indexTypeData = scalar(); + auto indexTypeIt = intTypeLookup.find(indexType); + + if(indexTypeIt != intTypeLookup.end()) + { + indexTypeData = indexTypeIt->second; + } + else + { + RDCERR("Unknown index type ID %u, defaulting to uint32_t", indexType); + } + + // if it's signed, bitcast it to unsigned + if(indexTypeData.signedness) + { + indexTypeData.signedness = false; + + SPIRVId unsignedIndex = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpBitcast, + {editor.DeclareType(indexTypeData), unsignedIndex, index})); + it++; + + index = unsignedIndex; + } + + // if it's not wide enough, uconvert expand it + if(indexTypeData.width != targetIndexWidth) + { + SPIRVId extendedtype = + editor.DeclareType(SPIRVScalar(spv::OpTypeInt, targetIndexWidth, false)); + SPIRVId extendedindex = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpUConvert, {extendedtype, extendedindex, index})); + it++; + + index = extendedindex; + } + } + + SPIRVId bufptr; + + if(useBufferAddress) + { + // convert the constant embedded device address to a pointer + + // get our output slot address by adding an offset to the base pointer + // baseaddr = bufferAddressConst + bindingOffset + SPIRVId baseaddr = editor.MakeId(); + editor.AddOperation(it, SPIRVOperation(spv::OpIAdd, {uint64ID, baseaddr, + bufferAddressConst, varIt->second})); + it++; + + // shift the index since this is a byte offset + // shiftedindex = index << uint32shift + SPIRVId shiftedindex = editor.MakeId(); + editor.AddOperation(it, SPIRVOperation(spv::OpShiftLeftLogical, + {uint64ID, shiftedindex, index, uint32shift})); + it++; + + // add the index on top of that + // offsetaddr = baseaddr + shiftedindex + SPIRVId offsetaddr = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpIAdd, {uint64ID, offsetaddr, baseaddr, shiftedindex})); + it++; + + // make a pointer out of it + // uint32_t *bufptr = (uint32_t *)offsetaddr + bufptr = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpConvertUToPtr, {uint32ptrtype, bufptr, offsetaddr})); + it++; + } + else + { + // accesschain into the SSBO, by adding the base offset for this var onto the index + + // add the index to this binding's base index + // ssboindex = bindingOffset + index + SPIRVId ssboindex = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpIAdd, {uint32ID, ssboindex, index, varIt->second})); + it++; + + // accesschain to get the pointer we'll atomic into. + // accesschain is 0 to access rtarray (first member) then ssboindex for array index + // uint32_t *bufptr = (uint32_t *)&buf.rtarray[ssboindex]; + bufptr = editor.MakeId(); + editor.AddOperation( + it, SPIRVOperation(spv::OpAccessChain, + {uint32ptrtype, bufptr, ssboVar, rtarrayOffset, ssboindex})); + it++; + } + + // atomically set the uint32 that's pointed to + editor.AddOperation( + it, SPIRVOperation(spv::OpAtomicUMax, + {uint32ID, editor.MakeId(), bufptr, scope, semantics, usedValue})); + + // no it++ here, it will happen implicitly on loop continue + } + } + } + } +} + +void VulkanReplay::ClearFeedbackCache() +{ + m_BindlessFeedback.Usage.clear(); +} + +void VulkanReplay::FetchShaderFeedback(uint32_t eventId) +{ + if(m_BindlessFeedback.Usage.find(eventId) != m_BindlessFeedback.Usage.end()) + return; + + // create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid + // if it actually has any data in it later. + DynamicUsedBinds &result = m_BindlessFeedback.Usage[eventId]; + + bool useBufferAddress = + ObjDisp(m_Device)->GetBufferDeviceAddressEXT && m_pDriver->GetDeviceFeatures().shaderInt64; + + const VulkanRenderState &state = m_pDriver->m_RenderState; + VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo; + + const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId); + + if(drawcall == NULL || !(drawcall->flags & (DrawFlags::Dispatch | DrawFlags::Drawcall))) + return; + + result.compute = bool(drawcall->flags & DrawFlags::Dispatch); + + const VulkanStatePipeline &pipe = result.compute ? state.compute : state.graphics; + + if(pipe.pipeline == ResourceId()) + return; + + const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[pipe.pipeline]; + + VkDeviceSize feedbackStorageSize = 0; + + std::map offsetMap; + + { + const std::vector &descSetLayoutIds = + creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts; + + SPIRVBinding key; + + for(size_t set = 0; set < descSetLayoutIds.size(); set++) + { + key.set = (uint32_t)set; + + const DescSetLayout &layout = creationInfo.m_DescSetLayout[descSetLayoutIds[set]]; + + for(size_t binding = 0; binding < layout.bindings.size(); binding++) + { + const DescSetLayout::Binding &bindData = layout.bindings[binding]; + + // skip empty bindings + if(bindData.descriptorCount == 0 || bindData.stageFlags == 0) + continue; + + // only process array bindings + if(bindData.descriptorCount > 1) + { + key.binding = (uint32_t)binding; + + offsetMap[key] = {feedbackStorageSize, bindData.descriptorCount}; + + feedbackStorageSize += bindData.descriptorCount * sizeof(uint32_t); + } + } + } + } + + // if we don't have any array descriptors to feedback then just return now + if(offsetMap.empty()) + return; + + // we go through the driver for all these creations since they need to be properly + // registered in order to be put in the partial replay state + VkResult vkr = VK_SUCCESS; + VkDevice dev = m_Device; + + VkGraphicsPipelineCreateInfo graphicsInfo = {}; + VkComputePipelineCreateInfo computeInfo = {}; + + // get pipeline create info + if(result.compute) + m_pDriver->GetShaderCache()->MakeComputePipelineInfo(computeInfo, state.compute.pipeline); + else + m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(graphicsInfo, state.graphics.pipeline); + + if(feedbackStorageSize > m_BindlessFeedback.FeedbackBuffer.sz) + { + uint32_t flags = GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO; + + if(useBufferAddress) + flags |= GPUBuffer::eGPUBufferAddressable; + + m_BindlessFeedback.FeedbackBuffer.Destroy(); + m_BindlessFeedback.FeedbackBuffer.Create(m_pDriver, dev, feedbackStorageSize, 1, flags); + } + + VkDeviceAddress bufferAddress = 0; + + VkDescriptorPool descpool = VK_NULL_HANDLE; + std::vector setLayouts; + std::vector descSets; + + VkPipelineLayout pipeLayout = VK_NULL_HANDLE; + + if(useBufferAddress) + { + VkBufferDeviceAddressInfoEXT getAddressInfo = {VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT}; + getAddressInfo.buffer = m_BindlessFeedback.FeedbackBuffer.buf; + + bufferAddress = m_pDriver->vkGetBufferDeviceAddressEXT(dev, &getAddressInfo); + } + else + { + VkDescriptorSetLayoutBinding newBindings[] = { + // output buffer + { + 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + VkShaderStageFlags(result.compute ? VK_SHADER_STAGE_COMPUTE_BIT + : VK_SHADER_STAGE_ALL_GRAPHICS), + NULL, + }, + }; + RDCCOMPILE_ASSERT(ARRAY_COUNT(newBindings) == 1, + "Should only be one new descriptor for bindless feedback"); + + // create a duplicate set of descriptor sets, all visible to compute, with bindings shifted to + // account for new ones we need. This also copies the existing bindings into the new sets + PatchReservedDescriptors(pipe, descpool, setLayouts, descSets, VkShaderStageFlagBits(), + newBindings, ARRAY_COUNT(newBindings)); + + // create pipeline layout with new descriptor set layouts + { + const std::vector &push = + creationInfo.m_PipelineLayout[pipeInfo.layout].pushRanges; + + VkPipelineLayoutCreateInfo pipeLayoutInfo = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + NULL, + 0, + (uint32_t)setLayouts.size(), + setLayouts.data(), + (uint32_t)push.size(), + push.data(), + }; + + vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &pipeLayout); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // we'll only use one, set both structs to keep things simple + computeInfo.layout = pipeLayout; + graphicsInfo.layout = pipeLayout; + } + + // vkUpdateDescriptorSet desc set to point to buffer + VkDescriptorBufferInfo desc = {0}; + + m_BindlessFeedback.FeedbackBuffer.FillDescriptor(desc); + + VkWriteDescriptorSet write = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + NULL, + Unwrap(descSets[0]), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + NULL, + &desc, + NULL, + }; + + ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), 1, &write, 0, NULL); + } + + // create vertex shader with modified code + VkShaderModuleCreateInfo moduleCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + + VkShaderModule modules[6] = {}; + + if(result.compute) + { + VkPipelineShaderStageCreateInfo &stage = computeInfo.stage; + + const VulkanCreationInfo::ShaderModule &moduleInfo = + creationInfo.m_ShaderModule[pipeInfo.shaders[5].module]; + + std::vector modSpirv = moduleInfo.spirv.spirv; + + AnnotateShader(*pipeInfo.shaders[5].patchData, stage.pName, offsetMap, bufferAddress, modSpirv); + + moduleCreateInfo.pCode = modSpirv.data(); + moduleCreateInfo.codeSize = modSpirv.size() * sizeof(uint32_t); + + vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &modules[0]); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + stage.module = modules[0]; + } + else + { + for(uint32_t i = 0; i < graphicsInfo.stageCount; i++) + { + VkPipelineShaderStageCreateInfo &stage = + (VkPipelineShaderStageCreateInfo &)graphicsInfo.pStages[i]; + + int idx = StageIndex(stage.stage); + + const VulkanCreationInfo::ShaderModule &moduleInfo = + creationInfo.m_ShaderModule[pipeInfo.shaders[idx].module]; + + std::vector modSpirv = moduleInfo.spirv.spirv; + + AnnotateShader(*pipeInfo.shaders[idx].patchData, stage.pName, offsetMap, bufferAddress, + modSpirv); + + moduleCreateInfo.pCode = modSpirv.data(); + moduleCreateInfo.codeSize = modSpirv.size() * sizeof(uint32_t); + + vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &modules[i]); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + stage.module = modules[i]; + } + } + + VkPipeline feedbackPipe; + + if(result.compute) + { + vkr = m_pDriver->vkCreateComputePipelines(m_Device, VK_NULL_HANDLE, 1, &computeInfo, NULL, + &feedbackPipe); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + else + { + vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &graphicsInfo, NULL, + &feedbackPipe); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + + // make copy of state to draw from + VulkanRenderState modifiedstate = state; + VulkanStatePipeline &modifiedpipe = result.compute ? modifiedstate.compute : modifiedstate.graphics; + + // bind created pipeline to partial replay state + modifiedpipe.pipeline = GetResID(feedbackPipe); + + if(!useBufferAddress) + { + // replace descriptor set IDs with our temporary sets. The offsets we keep the same. If the + // original draw had no sets, we ensure there's room (with no offsets needed) + + if(modifiedpipe.descSets.empty()) + modifiedpipe.descSets.resize(1); + + for(size_t i = 0; i < descSets.size(); i++) + modifiedpipe.descSets[i].descSet = GetResID(descSets[i]); + } + + { + VkCommandBuffer cmd = m_pDriver->GetNextCmd(); + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // fill destination buffer with 0s to ensure a baseline to then feedback against + ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(m_BindlessFeedback.FeedbackBuffer.buf), 0, + feedbackStorageSize, 0); + + VkBufferMemoryBarrier feedbackbufBarrier = { + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + NULL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_SHADER_WRITE_BIT, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + Unwrap(m_BindlessFeedback.FeedbackBuffer.buf), + 0, + feedbackStorageSize, + }; + + // wait for the above fill to finish. + DoPipelineBarrier(cmd, 1, &feedbackbufBarrier); + + if(result.compute) + { + modifiedstate.BindPipeline(cmd, VulkanRenderState::BindCompute, true); + + ObjDisp(cmd)->CmdDispatch(Unwrap(cmd), drawcall->dispatchDimension[0], + drawcall->dispatchDimension[1], drawcall->dispatchDimension[2]); + } + else + { + modifiedstate.BeginRenderPassAndApplyState(cmd, VulkanRenderState::BindGraphics); + + if(drawcall->flags & DrawFlags::Indexed) + { + ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances, + drawcall->indexOffset, drawcall->baseVertex, + drawcall->instanceOffset); + } + else + { + ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + } + + modifiedstate.EndRenderPass(cmd); + } + + vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + m_pDriver->SubmitCmds(); + m_pDriver->FlushQ(); + } + + bytebuf data; + GetBufferData(GetResID(m_BindlessFeedback.FeedbackBuffer.buf), 0, 0, data); + + for(auto it = offsetMap.begin(); it != offsetMap.end(); ++it) + { + uint32_t *feedbackData = (uint32_t *)(data.data() + it->second.offset); + + BindIdx used; + used.set = it->first.set; + used.bind = it->first.binding; + + for(uint32_t i = 0; i < it->second.numEntries; i++) + { + if(feedbackData[i]) + { + used.arrayidx = i; + + result.used.push_back(used); + } + } + } + + result.valid = true; + + // delete descriptors. Technically we don't have to free the descriptor sets, but our tracking on + // replay doesn't handle destroying children of pooled objects so we do it explicitly anyway. + m_pDriver->vkFreeDescriptorSets(dev, descpool, (uint32_t)descSets.size(), descSets.data()); + + m_pDriver->vkDestroyDescriptorPool(dev, descpool, NULL); + + for(VkDescriptorSetLayout layout : setLayouts) + m_pDriver->vkDestroyDescriptorSetLayout(dev, layout, NULL); + + // delete pipeline layout + m_pDriver->vkDestroyPipelineLayout(dev, pipeLayout, NULL); + + // delete pipeline + m_pDriver->vkDestroyPipeline(dev, feedbackPipe, NULL); + + // delete shader/shader module + for(size_t i = 0; i < ARRAY_COUNT(modules); i++) + if(modules[i] != VK_NULL_HANDLE) + m_pDriver->vkDestroyShaderModule(dev, modules[i], NULL); + + // replay from the start as we may have corrupted state while fetching the above feedback. + m_pDriver->ReplayLog(0, eventId, eReplay_Full); +} diff --git a/renderdoc/driver/vulkan/vk_common.cpp b/renderdoc/driver/vulkan/vk_common.cpp index 14dd44d22..4e6995cd9 100644 --- a/renderdoc/driver/vulkan/vk_common.cpp +++ b/renderdoc/driver/vulkan/vk_common.cpp @@ -162,6 +162,9 @@ void GPUBuffer::Create(WrappedVulkan *driver, VkDevice dev, VkDeviceSize size, u if(flags & eGPUBufferIndirectBuffer) bufInfo.usage |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; + if(flags & eGPUBufferAddressable) + bufInfo.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT; + VkResult vkr = driver->vkCreateBuffer(dev, &bufInfo, NULL, &buf); RDCASSERTEQUAL(vkr, VK_SUCCESS); diff --git a/renderdoc/driver/vulkan/vk_common.h b/renderdoc/driver/vulkan/vk_common.h index 1a2c843ba..3c7eb71f2 100644 --- a/renderdoc/driver/vulkan/vk_common.h +++ b/renderdoc/driver/vulkan/vk_common.h @@ -145,6 +145,7 @@ struct GPUBuffer eGPUBufferSSBO = 0x8, eGPUBufferGPULocal = 0x10, eGPUBufferIndirectBuffer = 0x20, + eGPUBufferAddressable = 0x40, }; void Create(WrappedVulkan *driver, VkDevice dev, VkDeviceSize size, uint32_t ringSize, diff --git a/renderdoc/driver/vulkan/vk_debug.cpp b/renderdoc/driver/vulkan/vk_debug.cpp index b43069748..8eb12747a 100644 --- a/renderdoc/driver/vulkan/vk_debug.cpp +++ b/renderdoc/driver/vulkan/vk_debug.cpp @@ -1633,6 +1633,7 @@ void VulkanReplay::CreateResources() void VulkanReplay::DestroyResources() { ClearPostVSCache(); + ClearFeedbackCache(); m_General.Destroy(m_pDriver); m_TexRender.Destroy(m_pDriver); @@ -2573,4 +2574,9 @@ void VulkanReplay::PostVS::Destroy(WrappedVulkan *driver) { if(XFBQueryPool != VK_NULL_HANDLE) driver->vkDestroyQueryPool(driver->GetDev(), XFBQueryPool, NULL); -} \ No newline at end of file +} + +void VulkanReplay::Feedback::Destroy(WrappedVulkan *driver) +{ + FeedbackBuffer.Destroy(); +} diff --git a/renderdoc/driver/vulkan/vk_postvs.cpp b/renderdoc/driver/vulkan/vk_postvs.cpp index e6f98d30f..8600f09cd 100644 --- a/renderdoc/driver/vulkan/vk_postvs.cpp +++ b/renderdoc/driver/vulkan/vk_postvs.cpp @@ -73,7 +73,7 @@ static void ConvertToMeshOutputCompute(const ShaderReflection &refl, const SPIRV // that requires knowing which variables are in set 0 and it's simpler to increase all bindings. if(it.opcode() == spv::OpDecorate && it.word(2) == spv::DecorationBinding) { - RDCASSERT(it.word(2) < (0xffffffff - MeshOutputReservedBindings)); + RDCASSERT(it.word(3) < (0xffffffff - MeshOutputReservedBindings)); it.word(3) += MeshOutputReservedBindings; } } @@ -1300,120 +1300,57 @@ void VulkanReplay::ClearPostVSCache() m_PostVS.Data.clear(); } -void VulkanReplay::FetchVSOut(uint32_t eventId) +void VulkanReplay::PatchReservedDescriptors(const VulkanStatePipeline &pipe, + VkDescriptorPool &descpool, + std::vector &setLayouts, + std::vector &descSets, + VkShaderStageFlagBits patchedBindingStage, + const VkDescriptorSetLayoutBinding *newBindings, + size_t newBindingsCount) { - const VulkanRenderState &state = m_pDriver->m_RenderState; + VkDevice dev = m_Device; VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo; - const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[state.graphics.pipeline]; + const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[pipe.pipeline]; - const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId); - - const VulkanCreationInfo::ShaderModule &moduleInfo = - creationInfo.m_ShaderModule[pipeInfo.shaders[0].module]; - - ShaderReflection *refl = pipeInfo.shaders[0].refl; - - // set defaults so that we don't try to fetch this output again if something goes wrong and the - // same event is selected again - { - m_PostVS.Data[eventId].vsin.topo = pipeInfo.topology; - m_PostVS.Data[eventId].vsout.buf = VK_NULL_HANDLE; - m_PostVS.Data[eventId].vsout.bufmem = VK_NULL_HANDLE; - m_PostVS.Data[eventId].vsout.instStride = 0; - m_PostVS.Data[eventId].vsout.vertStride = 0; - m_PostVS.Data[eventId].vsout.numViews = 1; - m_PostVS.Data[eventId].vsout.nearPlane = 0.0f; - m_PostVS.Data[eventId].vsout.farPlane = 0.0f; - m_PostVS.Data[eventId].vsout.useIndices = false; - m_PostVS.Data[eventId].vsout.hasPosOut = false; - m_PostVS.Data[eventId].vsout.idxbuf = VK_NULL_HANDLE; - m_PostVS.Data[eventId].vsout.idxbufmem = VK_NULL_HANDLE; - - m_PostVS.Data[eventId].vsout.topo = pipeInfo.topology; - } - - // no outputs from this shader? unexpected but theoretically possible (dummy VS before - // tessellation maybe). Just fill out an empty data set - if(refl->outputSignature.empty()) - return; - - // we go through the driver for all these creations since they need to be properly - // registered in order to be put in the partial replay state VkResult vkr = VK_SUCCESS; - VkDevice dev = m_Device; - VkDescriptorPool descpool; - std::vector setLayouts; - std::vector descSets; - - VkPipelineLayout pipeLayout; - - VkGraphicsPipelineCreateInfo pipeCreateInfo; - - // get pipeline create info - m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, state.graphics.pipeline); - - // create a duplicate set of descriptor sets, with all bindings shifted, and copy the bindings - // into them { std::vector descWrites; std::vector allocImgWrites; std::vector allocBufWrites; std::vector allocBufViewWrites; - // one for each descriptor type. 1 of each to start with plus enough for our internal resources, - // we then increment for each descriptor we need to allocate + // one for each descriptor type. 1 of each to start with, we then increment for each descriptor + // we need to allocate VkDescriptorPoolSize poolSizes[11] = { {VK_DESCRIPTOR_TYPE_SAMPLER, 1}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1}, - {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 50}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1}, {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1}, {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1}, }; + // count up our own + for(size_t i = 0; i < newBindingsCount; i++) + poolSizes[newBindings[i].descriptorType].descriptorCount += newBindings[i].descriptorCount; + const std::vector &descSetLayoutIds = creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts; - std::vector newBindings; - - // need to add our own bindings to the first descriptor set - { - // output buffer - newBindings.push_back({ - 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL, - }); - // index buffer (if needed) - newBindings.push_back({ - 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL, - }); - // vertex buffers (float type) - newBindings.push_back({ - 2, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, - VK_SHADER_STAGE_COMPUTE_BIT, NULL, - }); - // vertex buffers (uint32_t type) - newBindings.push_back({ - 3, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, - VK_SHADER_STAGE_COMPUTE_BIT, NULL, - }); - // vertex buffers (int32_t type) - newBindings.push_back({ - 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, - VK_SHADER_STAGE_COMPUTE_BIT, NULL, - }); - } + // need to add our added bindings to the first descriptor set + std::vector bindings(newBindings, newBindings + newBindingsCount); // if there are fewer sets bound than were declared in the pipeline layout, only process the // bound sets (as otherwise we'd fail to copy from them). Assume the application knew what it // was doing and the other sets are statically unused. - setLayouts.resize(RDCMIN(state.graphics.descSets.size(), descSetLayoutIds.size())); + setLayouts.resize(RDCMIN(pipe.descSets.size(), descSetLayoutIds.size())); // need at least one set, if the shader isn't using any we'll just make our own if(setLayouts.empty()) @@ -1425,7 +1362,7 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) // except for the first layout we need to start from scratch if(i > 0) - newBindings.clear(); + bindings.clear(); // if the shader had no descriptor sets at all, i will be invalid, so just skip and add a set // with only our own bindings. @@ -1445,8 +1382,9 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) poolSizes[bind.descriptorType].descriptorCount += bind.descriptorCount; VkDescriptorSetLayoutBinding newBind; - // offset the binding - newBind.binding = (uint32_t)b + MeshOutputReservedBindings; + // offset the binding. We offset all sets to make it easier for patching - don't need to + // conditionally patch shader bindings depending on which set they're in. + newBind.binding = uint32_t(b + newBindingsCount); newBind.descriptorCount = bind.descriptorCount; newBind.descriptorType = bind.descriptorType; @@ -1458,7 +1396,10 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) // Instead of trying to remap offsets to match, we simply make every binding compute // visible so the ordering is still the same. Since compute and graphics are disjoint this // is safe. - newBind.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + if(patchedBindingStage) + newBind.stageFlags = patchedBindingStage; + else + newBind.stageFlags = bind.stageFlags; if(bind.immutableSampler) { @@ -1474,7 +1415,7 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) newBind.pImmutableSamplers = NULL; } - newBindings.push_back(newBind); + bindings.push_back(newBind); } } @@ -1482,8 +1423,8 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL, 0, - (uint32_t)newBindings.size(), - newBindings.data(), + (uint32_t)bindings.size(), + bindings.data(), }; // create new offseted descriptor layout @@ -1492,7 +1433,7 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) if(hasImmutableSamplers) { - for(const VkDescriptorSetLayoutBinding &bind : newBindings) + for(const VkDescriptorSetLayoutBinding &bind : bindings) delete[] bind.pImmutableSamplers; } } @@ -1525,14 +1466,14 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) { const DescSetLayout &origLayout = creationInfo.m_DescSetLayout[descSetLayoutIds[i]]; - if(i >= state.graphics.descSets.size()) + if(i >= pipe.descSets.size()) continue; - if(state.graphics.descSets[i].descSet == ResourceId()) + if(pipe.descSets[i].descSet == ResourceId()) continue; WrappedVulkan::DescriptorSetInfo &setInfo = - m_pDriver->m_DescriptorSetState[state.graphics.descSets[i].descSet]; + m_pDriver->m_DescriptorSetState[pipe.descSets[i].descSet]; { // push descriptors don't have a source to copy from, we need to add writes @@ -1549,7 +1490,7 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) DescriptorSetSlot *slot = setInfo.currentBindings[b]; - write.dstBinding = (uint32_t)b + MeshOutputReservedBindings; + write.dstBinding = uint32_t(b + newBindingsCount); write.dstArrayElement = 0; write.descriptorCount = bind.descriptorCount; write.descriptorType = bind.descriptorType; @@ -1648,6 +1589,90 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) for(VkBufferView *a : allocBufViewWrites) delete[] a; } +} + +void VulkanReplay::FetchVSOut(uint32_t eventId) +{ + const VulkanRenderState &state = m_pDriver->m_RenderState; + VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo; + + const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[state.graphics.pipeline]; + + const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId); + + const VulkanCreationInfo::ShaderModule &moduleInfo = + creationInfo.m_ShaderModule[pipeInfo.shaders[0].module]; + + ShaderReflection *refl = pipeInfo.shaders[0].refl; + + // set defaults so that we don't try to fetch this output again if something goes wrong and the + // same event is selected again + { + m_PostVS.Data[eventId].vsin.topo = pipeInfo.topology; + m_PostVS.Data[eventId].vsout.buf = VK_NULL_HANDLE; + m_PostVS.Data[eventId].vsout.bufmem = VK_NULL_HANDLE; + m_PostVS.Data[eventId].vsout.instStride = 0; + m_PostVS.Data[eventId].vsout.vertStride = 0; + m_PostVS.Data[eventId].vsout.numViews = 1; + m_PostVS.Data[eventId].vsout.nearPlane = 0.0f; + m_PostVS.Data[eventId].vsout.farPlane = 0.0f; + m_PostVS.Data[eventId].vsout.useIndices = false; + m_PostVS.Data[eventId].vsout.hasPosOut = false; + m_PostVS.Data[eventId].vsout.idxbuf = VK_NULL_HANDLE; + m_PostVS.Data[eventId].vsout.idxbufmem = VK_NULL_HANDLE; + + m_PostVS.Data[eventId].vsout.topo = pipeInfo.topology; + } + + // no outputs from this shader? unexpected but theoretically possible (dummy VS before + // tessellation maybe). Just fill out an empty data set + if(refl->outputSignature.empty()) + return; + + // we go through the driver for all these creations since they need to be properly + // registered in order to be put in the partial replay state + VkResult vkr = VK_SUCCESS; + VkDevice dev = m_Device; + + VkDescriptorPool descpool; + std::vector setLayouts; + std::vector descSets; + + VkPipelineLayout pipeLayout; + + VkGraphicsPipelineCreateInfo pipeCreateInfo; + + // get pipeline create info + m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, state.graphics.pipeline); + + VkDescriptorSetLayoutBinding newBindings[] = { + // output buffer + { + 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + }, // index buffer (if needed) + { + 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + }, // vertex buffers (float type) + { + 2, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, + VK_SHADER_STAGE_COMPUTE_BIT, NULL, + }, // vertex buffers (uint32_t type) + { + 3, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, + VK_SHADER_STAGE_COMPUTE_BIT, NULL, + }, // vertex buffers (int32_t type) + { + 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MeshOutputTBufferArraySize, + VK_SHADER_STAGE_COMPUTE_BIT, NULL, + }, + }; + RDCCOMPILE_ASSERT(ARRAY_COUNT(newBindings) == MeshOutputReservedBindings, + "MeshOutputReservedBindings is wrong"); + + // create a duplicate set of descriptor sets, all visible to compute, with bindings shifted to + // account for new ones we need. This also copies the existing bindings into the new sets + PatchReservedDescriptors(m_pDriver->m_RenderState.graphics, descpool, setLayouts, descSets, + VK_SHADER_STAGE_COMPUTE_BIT, newBindings, ARRAY_COUNT(newBindings)); // create pipeline layout with new descriptor set layouts { @@ -2947,7 +2972,7 @@ void VulkanReplay::FetchTessGSOut(uint32_t eventId) ObjDisp(dev)->CmdResetQueryPool(Unwrap(cmd), Unwrap(m_PostVS.XFBQueryPool), 0, 1); // fill destination buffer with 0s to ensure unwritten vertices have sane data - ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(meshBuffer), 0, bufInfo.size, 0xbbaaddee); + ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(meshBuffer), 0, bufInfo.size, 0); VkBufferMemoryBarrier meshbufbarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 6a2b56418..95e0c3007 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -1349,7 +1349,7 @@ void VulkanReplay::SavePipelineState() &m_VulkanPipelineState.graphics.descriptorSets, &m_VulkanPipelineState.compute.descriptorSets, }; - const vector *srcs[] = { + const std::vector *srcs[] = { &state.graphics.descSets, &state.compute.descSets, }; @@ -3618,6 +3618,7 @@ void VulkanReplay::ReplaceResource(ResourceId from, ResourceId to) rm->ReplaceResource(liveid, to); ClearPostVSCache(); + ClearFeedbackCache(); } void VulkanReplay::RemoveReplacement(ResourceId id) @@ -3664,6 +3665,7 @@ void VulkanReplay::RemoveReplacement(ResourceId id) } ClearPostVSCache(); + ClearFeedbackCache(); } vector VulkanReplay::PixelHistory(vector events, ResourceId target, diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index 239f0e2f9..403b5193c 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -124,6 +124,7 @@ class AMDCounters; class WrappedVulkan; class VulkanDebugManager; class VulkanResourceManager; +struct VulkanStatePipeline; struct VulkanAMDDrawCallback; struct VulkanPostVSData @@ -182,6 +183,40 @@ struct VulkanPostVSData } }; +struct BindIdx +{ + uint32_t set, bind, arrayidx; + + bool operator<(const BindIdx &o) const + { + if(set != o.set) + return set < o.set; + else if(bind != o.bind) + return bind < o.bind; + return arrayidx < o.arrayidx; + } + + bool operator>(const BindIdx &o) const + { + if(set != o.set) + return set > o.set; + else if(bind != o.bind) + return bind > o.bind; + return arrayidx > o.arrayidx; + } + + bool operator==(const BindIdx &o) const + { + return set == o.set && bind == o.bind && arrayidx == o.arrayidx; + } +}; + +struct DynamicUsedBinds +{ + bool compute = false, valid = false; + std::vector used; +}; + class VulkanReplay : public IReplayDriver { public: @@ -262,8 +297,6 @@ public: // indicates that EID alias is the same as eventId void AliasPostVSBuffers(uint32_t eventId, uint32_t alias) { m_PostVS.Alias[alias] = eventId; } - void ClearPostVSCache(); - MeshFormat GetPostVSBuffers(uint32_t eventId, uint32_t instID, uint32_t viewID, MeshDataStage stage); @@ -344,8 +377,19 @@ public: AMDCounters *GetAMDCounters() { return m_pAMDCounters; } private: + void FetchShaderFeedback(uint32_t eventId); + void ClearFeedbackCache(); + + void PatchReservedDescriptors(const VulkanStatePipeline &pipe, VkDescriptorPool &descpool, + std::vector &setLayouts, + std::vector &descSets, + VkShaderStageFlagBits patchedBindingStage, + const VkDescriptorSetLayoutBinding *newBindings, + size_t newBindingsCount); + void FetchVSOut(uint32_t eventId); void FetchTessGSOut(uint32_t eventId); + void ClearPostVSCache(); bool RenderTextureInternal(TextureDisplay cfg, VkRenderPassBeginInfo rpbegin, int flags); @@ -607,6 +651,15 @@ private: std::map Alias; } m_PostVS; + struct Feedback + { + void Destroy(WrappedVulkan *driver); + + GPUBuffer FeedbackBuffer; + + std::map Usage; + } m_BindlessFeedback; + std::vector m_Resources; std::map m_ResourceIdx; diff --git a/renderdoc/driver/vulkan/vk_state.h b/renderdoc/driver/vulkan/vk_state.h index f0ebbd9ae..45d50c5f7 100644 --- a/renderdoc/driver/vulkan/vk_state.h +++ b/renderdoc/driver/vulkan/vk_state.h @@ -32,6 +32,18 @@ class VulkanResourceManager; class WrappedVulkan; struct DescSetLayout; +struct VulkanStatePipeline +{ + ResourceId pipeline; + + struct DescriptorAndOffsets + { + ResourceId descSet; + vector offsets; + }; + vector descSets; +}; + struct VulkanRenderState { enum PipelineBinding @@ -92,17 +104,7 @@ struct VulkanRenderState ResourceId framebuffer; VkRect2D renderArea; - struct Pipeline - { - ResourceId pipeline; - - struct DescriptorAndOffsets - { - ResourceId descSet; - vector offsets; - }; - vector descSets; - } compute, graphics; + VulkanStatePipeline compute, graphics; struct IdxBuffer { diff --git a/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp index 0ec59792d..7d33da217 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_cmd_funcs.cpp @@ -2007,7 +2007,7 @@ bool WrappedVulkan::Serialise_vkCmdBindDescriptorSets( if(ShouldUpdateRenderState(m_LastCmdBufferID)) { - std::vector &descsets = + std::vector &descsets = (pipelineBindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) ? m_RenderState.graphics.descSets : m_RenderState.compute.descSets; @@ -3617,7 +3617,7 @@ bool WrappedVulkan::Serialise_vkCmdPushDescriptorSetKHR(SerialiserType &ser, if(ShouldUpdateRenderState(m_LastCmdBufferID)) { - std::vector &descsets = + std::vector &descsets = (pipelineBindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) ? m_RenderState.graphics.descSets : m_RenderState.compute.descSets; @@ -3888,7 +3888,7 @@ bool WrappedVulkan::Serialise_vkCmdPushDescriptorSetWithTemplateKHR( if(ShouldUpdateRenderState(m_LastCmdBufferID)) { - std::vector &descsets = + std::vector &descsets = (bindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) ? m_RenderState.graphics.descSets : m_RenderState.compute.descSets; diff --git a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp index c649eb1c7..adc15b66b 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp @@ -1292,6 +1292,19 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi "geometry/tessellation stages will not be available"); } + if(supportedExtensions.find(VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) != + supportedExtensions.end()) + { + Extensions.push_back(VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + RDCLOG("Enabling VK_EXT_buffer_device_address"); + } + else + { + RDCWARN( + "VK_EXT_buffer_device_address not available, feedback from " + "bindless shader access will use less reliable fallback"); + } + createInfo.enabledLayerCount = (uint32_t)Layers.size(); const char **layerArray = NULL; @@ -1941,6 +1954,13 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi "robustBufferAccess = false, out of bounds access due to bugs in application or " "RenderDoc may cause crashes"); + if(availFeatures.shaderInt64) + enabledFeatures.shaderInt64 = true; + else + RDCWARN( + "shaderInt64 = false, feedback from bindless shader access will use less reliable " + "fallback."); + if(availFeatures.shaderStorageImageWriteWithoutFormat) enabledFeatures.shaderStorageImageWriteWithoutFormat = true; else