mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-13 18:21:01 +00:00
Feedback dynamic shader access to arrayed descriptors
* We use VK_EXT_buffer_device_address where possible to reduce code complexity & increase compatibility, but when not available we reserve a buffer within the existing bindings. * Only array descriptors have the feedback run. Non-array descriptors can still be dynamically used/unused but it's expected that there is less pressure to trim the list as this would only account for flow control and there is value in showing bindings that may be dynamically unused. For arrays the size might potentially be extremely large (with 'bindless' type arrays) so reducing it only to the set of used items is important.
This commit is contained in:
@@ -23,10 +23,399 @@
|
||||
******************************************************************************/
|
||||
|
||||
#include "spirv_editor.h"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#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<uint32_t> &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<SPIRVScalar, SPIRVId> &SPIRVEditor::GetTable<SPIRVScalar>()
|
||||
{
|
||||
return scalarTypes;
|
||||
}
|
||||
#define TYPETABLE(StructType, variable) \
|
||||
template <> \
|
||||
std::map<StructType, SPIRVId> &SPIRVEditor::GetTable<StructType>() \
|
||||
{ \
|
||||
return variable; \
|
||||
} \
|
||||
template <> \
|
||||
const std::map<StructType, SPIRVId> &SPIRVEditor::GetTable<StructType>() const \
|
||||
{ \
|
||||
return variable; \
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVVector, SPIRVId> &SPIRVEditor::GetTable<SPIRVVector>()
|
||||
{
|
||||
return vectorTypes;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVMatrix, SPIRVId> &SPIRVEditor::GetTable<SPIRVMatrix>()
|
||||
{
|
||||
return matrixTypes;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVPointer, SPIRVId> &SPIRVEditor::GetTable<SPIRVPointer>()
|
||||
{
|
||||
return pointerTypes;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVImage, SPIRVId> &SPIRVEditor::GetTable<SPIRVImage>()
|
||||
{
|
||||
return imageTypes;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVSampledImage, SPIRVId> &SPIRVEditor::GetTable<SPIRVSampledImage>()
|
||||
{
|
||||
return sampledImageTypes;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::map<SPIRVFunction, SPIRVId> &SPIRVEditor::GetTable<SPIRVFunction>()
|
||||
{
|
||||
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)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <vector>
|
||||
#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 <typename SPIRVType>
|
||||
using SPIRVTypeId = std::pair<SPIRVType, SPIRVId>;
|
||||
|
||||
template <typename SPIRVType>
|
||||
using SPIRVTypeIds = std::vector<SPIRVTypeId<SPIRVType>>;
|
||||
|
||||
// hack around enum class being useless for array indices :(
|
||||
struct SPIRVSection
|
||||
{
|
||||
@@ -462,6 +553,33 @@ public:
|
||||
return SPIRVId();
|
||||
}
|
||||
|
||||
template <typename SPIRVType>
|
||||
SPIRVTypeIds<SPIRVType> GetTypes()
|
||||
{
|
||||
std::map<SPIRVType, SPIRVId> &table = GetTable<SPIRVType>();
|
||||
|
||||
SPIRVTypeIds<SPIRVType> ret;
|
||||
|
||||
for(auto it = table.begin(); it != table.end(); ++it)
|
||||
ret.push_back(*it);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename SPIRVType>
|
||||
const std::map<SPIRVType, SPIRVId> &GetTypeInfo() const
|
||||
{
|
||||
return GetTable<SPIRVType>();
|
||||
}
|
||||
|
||||
SPIRVBinding GetBinding(SPIRVId id) const
|
||||
{
|
||||
auto it = bindings.find(id);
|
||||
if(it == bindings.end())
|
||||
return SPIRVBinding();
|
||||
return it->second;
|
||||
}
|
||||
const std::set<SPIRVId> &GetStructTypes() const { return structTypes; }
|
||||
SPIRVId DeclareStructType(std::vector<uint32_t> members);
|
||||
|
||||
// helper for AddConstant
|
||||
@@ -490,7 +608,9 @@ public:
|
||||
|
||||
// accessors to structs/vectors of data
|
||||
const std::vector<SPIRVEntry> &GetEntries() { return entries; }
|
||||
const std::vector<SPIRVVariable> &GetVariables() { return variables; }
|
||||
const std::vector<SPIRVId> &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<SPIRVDecoration> decorations;
|
||||
|
||||
std::map<SPIRVId, SPIRVBinding> bindings;
|
||||
|
||||
std::vector<size_t> idOffsets;
|
||||
std::vector<SPIRVId> idTypes;
|
||||
|
||||
std::vector<SPIRVEntry> entries;
|
||||
std::vector<SPIRVVariable> variables;
|
||||
std::vector<SPIRVId> functions;
|
||||
std::set<std::string> extensions;
|
||||
std::set<spv::Capability> capabilities;
|
||||
@@ -523,11 +649,17 @@ private:
|
||||
std::map<SPIRVMatrix, SPIRVId> matrixTypes;
|
||||
std::map<SPIRVPointer, SPIRVId> pointerTypes;
|
||||
std::map<SPIRVImage, SPIRVId> imageTypes;
|
||||
std::map<SPIRVSampler, SPIRVId> samplerTypes;
|
||||
std::map<SPIRVSampledImage, SPIRVId> sampledImageTypes;
|
||||
std::map<SPIRVFunction, SPIRVId> functionTypes;
|
||||
|
||||
std::set<SPIRVId> structTypes;
|
||||
|
||||
template <typename SPIRVType>
|
||||
std::map<SPIRVType, SPIRVId> &GetTable();
|
||||
|
||||
template <typename SPIRVType>
|
||||
const std::map<SPIRVType, SPIRVId> &GetTable() const;
|
||||
|
||||
std::vector<uint32_t> &spirv;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<ClCompile Include="vk_apple.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vk_bindless_feedback.cpp" />
|
||||
<ClCompile Include="vk_msaa_array_conv.cpp" />
|
||||
<ClCompile Include="vk_next_chains.cpp" />
|
||||
<ClCompile Include="vk_outputwindow.cpp" />
|
||||
|
||||
@@ -136,6 +136,9 @@
|
||||
<ClCompile Include="vk_next_chains.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vk_bindless_feedback.cpp">
|
||||
<Filter>Replay</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="vk_replay.h">
|
||||
|
||||
@@ -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 <float.h>
|
||||
#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<SPIRVBinding, feedbackData> &offsetMap, VkDeviceAddress addr,
|
||||
std::vector<uint32_t> &modSpirv)
|
||||
{
|
||||
SPIRVEditor editor(modSpirv);
|
||||
|
||||
const bool useBufferAddress = (addr != 0);
|
||||
|
||||
SPIRVId uint32ID = editor.DeclareType(scalar<uint32_t>());
|
||||
SPIRVId int32ID = editor.DeclareType(scalar<int32_t>());
|
||||
SPIRVId uint64ID, int64ID;
|
||||
SPIRVId uint32StructID;
|
||||
SPIRVId funcParamType;
|
||||
|
||||
if(useBufferAddress)
|
||||
{
|
||||
// declare the int64 types we'll need
|
||||
uint64ID = editor.DeclareType(scalar<uint64_t>());
|
||||
int64ID = editor.DeclareType(scalar<int64_t>());
|
||||
|
||||
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<SPIRVId, SPIRVId> 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<uint64_t>(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>(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<uint64_t>(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<uint32_t>(0U);
|
||||
SPIRVId usedValue = editor.AddConstantImmediate<uint32_t>(0xFFFFFFFFU);
|
||||
SPIRVId scope = editor.AddConstantImmediate<uint32_t>(spv::ScopeInvocation);
|
||||
SPIRVId semantics = editor.AddConstantImmediate<uint32_t>(0U);
|
||||
SPIRVId uint32shift = editor.AddConstantImmediate<uint32_t>(2U);
|
||||
|
||||
std::map<SPIRVId, SPIRVScalar> intTypeLookup;
|
||||
|
||||
for(auto scalarType : editor.GetTypeInfo<SPIRVScalar>())
|
||||
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<SPIRVFunction> funcTypes = editor.GetTypes<SPIRVFunction>();
|
||||
|
||||
// functions that have been patched with annotation & extra function parameters if needed
|
||||
std::set<SPIRVId> patchedFunctions;
|
||||
|
||||
// functions we need to patch, with the indices of which parameters have bindings coming along
|
||||
// with
|
||||
std::map<SPIRVId, std::vector<size_t>> 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<size_t> 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<SPIRVFunction> &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<SPIRVId> 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<uint32_t> funccall;
|
||||
std::vector<size_t> 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<uint32_t>();
|
||||
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<SPIRVBinding, feedbackData> offsetMap;
|
||||
|
||||
{
|
||||
const std::vector<ResourceId> &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<VkDescriptorSetLayout> setLayouts;
|
||||
std::vector<VkDescriptorSet> 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<VkPushConstantRange> &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<uint32_t> 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<uint32_t> 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanReplay::Feedback::Destroy(WrappedVulkan *driver)
|
||||
{
|
||||
FeedbackBuffer.Destroy();
|
||||
}
|
||||
|
||||
@@ -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<VkDescriptorSetLayout> &setLayouts,
|
||||
std::vector<VkDescriptorSet> &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<VkDescriptorSetLayout> setLayouts;
|
||||
std::vector<VkDescriptorSet> 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<VkWriteDescriptorSet> descWrites;
|
||||
std::vector<VkDescriptorImageInfo *> allocImgWrites;
|
||||
std::vector<VkDescriptorBufferInfo *> allocBufWrites;
|
||||
std::vector<VkBufferView *> 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<ResourceId> &descSetLayoutIds =
|
||||
creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts;
|
||||
|
||||
std::vector<VkDescriptorSetLayoutBinding> 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<VkDescriptorSetLayoutBinding> 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<VkDescriptorSetLayout> setLayouts;
|
||||
std::vector<VkDescriptorSet> 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,
|
||||
|
||||
@@ -1349,7 +1349,7 @@ void VulkanReplay::SavePipelineState()
|
||||
&m_VulkanPipelineState.graphics.descriptorSets, &m_VulkanPipelineState.compute.descriptorSets,
|
||||
};
|
||||
|
||||
const vector<VulkanRenderState::Pipeline::DescriptorAndOffsets> *srcs[] = {
|
||||
const std::vector<VulkanStatePipeline::DescriptorAndOffsets> *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<PixelModification> VulkanReplay::PixelHistory(vector<EventUsage> events, ResourceId target,
|
||||
|
||||
@@ -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<BindIdx> 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<VkDescriptorSetLayout> &setLayouts,
|
||||
std::vector<VkDescriptorSet> &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<uint32_t, uint32_t> Alias;
|
||||
} m_PostVS;
|
||||
|
||||
struct Feedback
|
||||
{
|
||||
void Destroy(WrappedVulkan *driver);
|
||||
|
||||
GPUBuffer FeedbackBuffer;
|
||||
|
||||
std::map<uint32_t, DynamicUsedBinds> Usage;
|
||||
} m_BindlessFeedback;
|
||||
|
||||
std::vector<ResourceDescription> m_Resources;
|
||||
std::map<ResourceId, size_t> m_ResourceIdx;
|
||||
|
||||
|
||||
@@ -32,6 +32,18 @@ class VulkanResourceManager;
|
||||
class WrappedVulkan;
|
||||
struct DescSetLayout;
|
||||
|
||||
struct VulkanStatePipeline
|
||||
{
|
||||
ResourceId pipeline;
|
||||
|
||||
struct DescriptorAndOffsets
|
||||
{
|
||||
ResourceId descSet;
|
||||
vector<uint32_t> offsets;
|
||||
};
|
||||
vector<DescriptorAndOffsets> descSets;
|
||||
};
|
||||
|
||||
struct VulkanRenderState
|
||||
{
|
||||
enum PipelineBinding
|
||||
@@ -92,17 +104,7 @@ struct VulkanRenderState
|
||||
ResourceId framebuffer;
|
||||
VkRect2D renderArea;
|
||||
|
||||
struct Pipeline
|
||||
{
|
||||
ResourceId pipeline;
|
||||
|
||||
struct DescriptorAndOffsets
|
||||
{
|
||||
ResourceId descSet;
|
||||
vector<uint32_t> offsets;
|
||||
};
|
||||
vector<DescriptorAndOffsets> descSets;
|
||||
} compute, graphics;
|
||||
VulkanStatePipeline compute, graphics;
|
||||
|
||||
struct IdxBuffer
|
||||
{
|
||||
|
||||
@@ -2007,7 +2007,7 @@ bool WrappedVulkan::Serialise_vkCmdBindDescriptorSets(
|
||||
|
||||
if(ShouldUpdateRenderState(m_LastCmdBufferID))
|
||||
{
|
||||
std::vector<VulkanRenderState::Pipeline::DescriptorAndOffsets> &descsets =
|
||||
std::vector<VulkanStatePipeline::DescriptorAndOffsets> &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<VulkanRenderState::Pipeline::DescriptorAndOffsets> &descsets =
|
||||
std::vector<VulkanStatePipeline::DescriptorAndOffsets> &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<VulkanRenderState::Pipeline::DescriptorAndOffsets> &descsets =
|
||||
std::vector<VulkanStatePipeline::DescriptorAndOffsets> &descsets =
|
||||
(bindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) ? m_RenderState.graphics.descSets
|
||||
: m_RenderState.compute.descSets;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user