diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index 3cd83def0..f32011d4f 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -412,9 +412,10 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderChangeStats) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ResourceBindStats) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, SamplerBindStats) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ConstantBindStats) -TEMPLATE_ARRAY_INSTANTIATE(rdcarray, Descriptor) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DescriptorRange) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, Descriptor) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, SamplerDescriptor) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DescriptorAccess) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, Attachment) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, BindingElement) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, DescriptorBinding) diff --git a/renderdoc/api/replay/common_pipestate.h b/renderdoc/api/replay/common_pipestate.h index af55da5e9..2a3011b24 100644 --- a/renderdoc/api/replay/common_pipestate.h +++ b/renderdoc/api/replay/common_pipestate.h @@ -25,6 +25,7 @@ #pragma once #include "apidefs.h" +#include "data_types.h" #include "rdcarray.h" #include "shader_types.h" #include "stringise.h" @@ -746,6 +747,108 @@ this sampler. DECLARE_REFLECTION_STRUCT(SamplerDescriptor); +DOCUMENT(R"(The details of a single accessed descriptor as fetched by a shader and which descriptor +in the descriptor store was fetched. + +This may be a somewhat conservative access, reported as possible but not actually executed on the +GPU itself. + +.. data:: NoShaderBinding + + No shader binding corresponds to this descriptor access, it happened directly without going + through any kind of binding. +)"); +struct DescriptorAccess +{ + DOCUMENT(""); + DescriptorAccess() = default; + DescriptorAccess(const DescriptorAccess &) = default; + DescriptorAccess &operator=(const DescriptorAccess &) = default; + + bool operator==(const DescriptorAccess &o) const + { + return stage == o.stage && type == o.type && index == o.index && + arrayElement == o.arrayElement && descriptorStore == o.descriptorStore && + byteOffset == o.byteOffset && byteSize == o.byteSize; + } + bool operator<(const DescriptorAccess &o) const + { + if(stage != o.stage) + return stage < o.stage; + if(type != o.type) + return type < o.type; + if(index != o.index) + return index < o.index; + if(arrayElement != o.arrayElement) + return arrayElement < o.arrayElement; + if(descriptorStore != o.descriptorStore) + return descriptorStore < o.descriptorStore; + if(byteOffset != o.byteOffset) + return byteOffset < o.byteOffset; + if(byteSize != o.byteSize) + return byteSize < o.byteSize; + return false; + } + + DOCUMENT(R"(The shader stage that this descriptor access came from. + +:type: ShaderStage +)"); + ShaderStage stage = ShaderStage::Count; + DOCUMENT(R"(The type of the descriptor being accessed. + +:type: DescriptorType +)"); + DescriptorType type = DescriptorType::Unknown; + + DOCUMENT(R"(The index within the shader's reflection list corresponding to :data:`type` of the +accessing resource. + +If this value is set to :data:`NoShaderBinding` then the shader synthesised a direct access into +descriptor storage without passing through a declared binding. + +:type: int +)"); + uint16_t index = 0; + + static const uint16_t NoShaderBinding = 0xFFFF; + + DOCUMENT(R"(For an arrayed resource declared in a shader, the array element used. + +:type: int +)"); + uint32_t arrayElement = 0; + + DOCUMENT(R"(The backing storage of the descriptor. + +:type: ResourceId +)"); + ResourceId descriptorStore; + DOCUMENT(R"(The offset in bytes to the descriptor in the descriptor store. + +:type: int +)"); + uint32_t byteOffset = 0; + DOCUMENT(R"(The size in bytes of the descriptor. + +:type: int +)"); + uint32_t byteSize = 0; + + DOCUMENT(R"(For informational purposes, some descriptors that are declared in the shader +interface but are provably unused may still be reported as descriptor accesses. This flag will be +set to ``True`` to indicate that the descriptor was definitely not used. + +This flag only states that a descriptor is definitely unused on all paths. If set to ``False`` this +does not necessarily guarantee that the descriptor was accessed on the GPU during execution. + +:type: bool +)"); + bool staticallyUnused = false; +}; + +DECLARE_REFLECTION_STRUCT(DescriptorAccess); + DOCUMENT("Information about a single constant buffer binding."); struct BoundCBuffer { diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index 0a5457bcf..8cc0883e8 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -27,6 +27,7 @@ #include #include "apidefs.h" +#include "common_pipestate.h" #include "data_types.h" #include "rdcarray.h" #include "replay_enums.h" @@ -638,6 +639,12 @@ struct DescriptorRange DescriptorRange(const DescriptorRange &) = default; DescriptorRange &operator=(const DescriptorRange &) = default; + DescriptorRange(const DescriptorAccess &access) + { + offset = access.byteOffset; + descriptorSize = access.byteSize; + } + DOCUMENT("The offset in the descriptor storage where the descriptor range starts."); uint32_t offset = 0; DOCUMENT("The size of each descriptor in the range."); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index b5fb4e553..fb0024900 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -588,6 +588,13 @@ Multiple ranges within the store can be queried at once, and are returned in a c virtual rdcarray GetSamplerDescriptors( ResourceId descriptorStore, const rdcarray &ranges) = 0; + DOCUMENT(R"(Retrieve the descriptor accesses that happened at the current event. + +:return: The descriptor accesses. +:rtype: List[DescriptorAccess] +)"); + virtual rdcarray GetDescriptorAccess() = 0; + DOCUMENT(R"(Retrieve the list of possible disassembly targets for :meth:`DisassembleShader`. The values are implementation dependent but will always include a default target first which is the native disassembly of the shader. Further options may be available for additional diassembly views diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 7ed3692ad..3a9754ff7 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -907,6 +907,38 @@ enum class DescriptorType : uint8_t DECLARE_REFLECTION_ENUM(DescriptorType); +DOCUMENT("Checks if a descriptor type corresponds to a constant buffer in shader reflection."); +constexpr bool IsConstantBufferDescriptor(DescriptorType type) +{ + return type == DescriptorType::ConstantBuffer; +} + +DOCUMENT(R"(Checks if a descriptor type corresponds to a sampler in shader reflection. Only dedicated +sampler types are sampler descriptors, combined image/samplers are reported only as read only +resources. +)"); +constexpr bool IsSamplerDescriptor(DescriptorType type) +{ + return type == DescriptorType::Sampler; +} + +DOCUMENT(R"(Checks if a descriptor type corresponds to a read only resource in shader reflection. +Combined image/samplers are reported as read only resources. +)"); +constexpr bool IsReadOnlyDescriptor(DescriptorType type) +{ + return type == DescriptorType::ImageSampler || type == DescriptorType::Image || + type == DescriptorType::TypedBuffer; +} + +DOCUMENT(R"(Checks if a descriptor type corresponds to a read write resource in shader reflection. +)"); +constexpr bool IsReadWriteDescriptor(DescriptorType type) +{ + return type == DescriptorType::ReadWriteBuffer || type == DescriptorType::ReadWriteImage || + type == DescriptorType::ReadWriteTypedBuffer; +} + DOCUMENT3(R"(Annotates a particular built-in input or output from a shader with a special meaning to the hardware or API. @@ -2472,7 +2504,7 @@ DOCUMENT(R"(The stage in a pipeline where a shader runs The mesh shader. )"); -enum class ShaderStage : uint32_t +enum class ShaderStage : uint8_t { Vertex = 0, First = Vertex, diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index 72b31990d..6add0d974 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -279,6 +279,7 @@ public: ret.resize(count); return ret; } + rdcarray GetDescriptorAccess() { return {}; } DriverInformation GetDriverInfo() { DriverInformation ret = {}; diff --git a/renderdoc/core/replay_proxy.cpp b/renderdoc/core/replay_proxy.cpp index e7cc6b70c..82a12bb11 100644 --- a/renderdoc/core/replay_proxy.cpp +++ b/renderdoc/core/replay_proxy.cpp @@ -100,6 +100,7 @@ rdcstr DoStringise(const ReplayProxyPacket &el) STRINGISE_ENUM_NAMED(eReplayProxy_GetDescriptors, "GetDescriptors"); STRINGISE_ENUM_NAMED(eReplayProxy_GetSamplerDescriptors, "GetSamplerDescriptors"); + STRINGISE_ENUM_NAMED(eReplayProxy_GetDescriptorAccess, "GetDescriptorAccess"); } END_ENUM_STRINGISE(); } @@ -1906,6 +1907,35 @@ rdcarray ReplayProxy::GetSamplerDescriptors(ResourceId descri PROXY_FUNCTION(GetSamplerDescriptors, descriptorStore, ranges); } +template +rdcarray ReplayProxy::Proxied_GetDescriptorAccess(ParamSerialiser ¶mser, + ReturnSerialiser &retser) +{ + const ReplayProxyPacket expectedPacket = eReplayProxy_GetDescriptorAccess; + ReplayProxyPacket packet = eReplayProxy_GetDescriptorAccess; + rdcarray ret; + + { + BEGIN_PARAMS(); + END_PARAMS(); + } + + { + REMOTE_EXECUTION(); + if(paramser.IsReading() && !paramser.IsErrored() && !m_IsErrored) + ret = m_Remote->GetDescriptorAccess(); + } + + SERIALISE_RETURN(ret); + + return ret; +} + +rdcarray ReplayProxy::GetDescriptorAccess() +{ + PROXY_FUNCTION(GetDescriptorAccess); +} + template void ReplayProxy::Proxied_ReplayLog(ParamSerialiser ¶mser, ReturnSerialiser &retser, uint32_t endEventID, ReplayLogType replayType) @@ -2978,6 +3008,7 @@ bool ReplayProxy::Tick(int type) case eReplayProxy_SavePipelineState: SavePipelineState(0); break; case eReplayProxy_GetDescriptors: GetDescriptors(ResourceId(), {}); break; case eReplayProxy_GetSamplerDescriptors: GetSamplerDescriptors(ResourceId(), {}); break; + case eReplayProxy_GetDescriptorAccess: GetDescriptorAccess(); break; case eReplayProxy_GetUsage: GetUsage(ResourceId()); break; case eReplayProxy_GetLiveID: GetLiveID(ResourceId()); break; case eReplayProxy_GetFrameRecord: GetFrameRecord(); break; diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index d779acc0a..fa1a2e524 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -108,6 +108,7 @@ enum ReplayProxyPacket eReplayProxy_GetDescriptors, eReplayProxy_GetSamplerDescriptors, + eReplayProxy_GetDescriptorAccess, }; DECLARE_REFLECTION_ENUM(ReplayProxyPacket); @@ -485,6 +486,7 @@ public: const rdcarray &ranges); IMPLEMENT_FUNCTION_PROXIED(rdcarray, GetSamplerDescriptors, ResourceId descriptorStore, const rdcarray &ranges); + IMPLEMENT_FUNCTION_PROXIED(rdcarray, GetDescriptorAccess); IMPLEMENT_FUNCTION_PROXIED(rdcarray, GetPassEvents, uint32_t eventId); diff --git a/renderdoc/data/glsl_shaders.h b/renderdoc/data/glsl_shaders.h index 68ec15c23..137c6cd64 100644 --- a/renderdoc/data/glsl_shaders.h +++ b/renderdoc/data/glsl_shaders.h @@ -41,7 +41,7 @@ rdcstr InsertSnippetAfterVersion(ShaderType type, const char *source, int len, c // for unit tests struct ShaderReflection; struct ShaderBindpointMapping; -enum class ShaderStage : uint32_t; +enum class ShaderStage : uint8_t; using ReflectionMaker = std::function; diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index 55f05c16c..5abdd1ec0 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -1982,6 +1982,11 @@ rdcarray D3D11Replay::GetSamplerDescriptors(ResourceId descri return ret; } +rdcarray D3D11Replay::GetDescriptorAccess() +{ + return {}; +} + RDResult D3D11Replay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { return m_pDevice->ReadLogInitialisation(rdc, storeStructuredBuffers); diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index 0810be96e..00c67ebed 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -186,6 +186,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); void FreeTargetResource(ResourceId id); void FreeCustomShader(ResourceId id); diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 23f9d977e..f10387cd4 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -2427,6 +2427,11 @@ rdcarray D3D12Replay::GetSamplerDescriptors(ResourceId descri return ret; } +rdcarray D3D12Replay::GetDescriptorAccess() +{ + return {}; +} + void D3D12Replay::RenderHighlightBox(float w, float h, float scale) { OutputWindow &outw = m_OutputWindows[m_CurrentOutputWindow]; diff --git a/renderdoc/driver/d3d12/d3d12_replay.h b/renderdoc/driver/d3d12/d3d12_replay.h index 256802f8b..1b4ebec2f 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.h +++ b/renderdoc/driver/d3d12/d3d12_replay.h @@ -142,6 +142,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); void FreeTargetResource(ResourceId id); void FreeCustomShader(ResourceId id); diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index bcbd198f9..0c642d985 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -2574,6 +2574,11 @@ rdcarray GLReplay::GetSamplerDescriptors(ResourceId descripto return ret; } +rdcarray GLReplay::GetDescriptorAccess() +{ + return {}; +} + void GLReplay::OpenGLFillCBufferVariables(ResourceId shader, GLuint prog, bool bufferBacked, rdcstr prefix, const rdcarray &variables, rdcarray &outvars, diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 755719a55..8f324600c 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -164,6 +164,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); void FreeTargetResource(ResourceId id); RDResult ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); diff --git a/renderdoc/driver/shaders/spirv/spirv_common.h b/renderdoc/driver/shaders/spirv/spirv_common.h index 526ec1dba..62b5786fa 100644 --- a/renderdoc/driver/shaders/spirv/spirv_common.h +++ b/renderdoc/driver/shaders/spirv/spirv_common.h @@ -453,7 +453,7 @@ struct SpecConstant DECLARE_STRINGISE_TYPE(rdcspv::Id); -enum class ShaderStage : uint32_t; +enum class ShaderStage : uint8_t; enum class ShaderBuiltin : uint32_t; ShaderStage MakeShaderStage(rdcspv::ExecutionModel model); diff --git a/renderdoc/driver/shaders/spirv/spirv_reflect.h b/renderdoc/driver/shaders/spirv/spirv_reflect.h index 55f1a8eb9..0ff2fa31d 100644 --- a/renderdoc/driver/shaders/spirv/spirv_reflect.h +++ b/renderdoc/driver/shaders/spirv/spirv_reflect.h @@ -29,7 +29,7 @@ #include "spirv_processor.h" enum class GraphicsAPI : uint32_t; -enum class ShaderStage : uint32_t; +enum class ShaderStage : uint8_t; enum class ShaderBuiltin : uint32_t; struct ShaderReflection; struct ShaderBindpointMapping; diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index d6b659194..fbaf911ee 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -2605,6 +2605,11 @@ rdcarray VulkanReplay::GetSamplerDescriptors(ResourceId descr return ret; } +rdcarray VulkanReplay::GetDescriptorAccess() +{ + return {}; +} + void VulkanReplay::FillCBufferVariables(ResourceId pipeline, ResourceId shader, ShaderStage stage, rdcstr entryPoint, uint32_t cbufSlot, rdcarray &outvars, const bytebuf &data) diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index 89ceebe96..6b4162f18 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -353,6 +353,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); void FreeTargetResource(ResourceId id); RDResult ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); diff --git a/renderdoc/replay/dummy_driver.cpp b/renderdoc/replay/dummy_driver.cpp index efa7de6e7..2a1e38afa 100644 --- a/renderdoc/replay/dummy_driver.cpp +++ b/renderdoc/replay/dummy_driver.cpp @@ -165,6 +165,11 @@ rdcarray DummyDriver::GetSamplerDescriptors(ResourceId descri return ret; } +rdcarray DummyDriver::GetDescriptorAccess() +{ + return {}; +} + FrameRecord DummyDriver::GetFrameRecord() { return m_FrameRecord; diff --git a/renderdoc/replay/dummy_driver.h b/renderdoc/replay/dummy_driver.h index 031705b9a..1b709af75 100644 --- a/renderdoc/replay/dummy_driver.h +++ b/renderdoc/replay/dummy_driver.h @@ -64,6 +64,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); FrameRecord GetFrameRecord(); diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index d0aaa8cc5..1c62274fe 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -1121,6 +1121,21 @@ void DoSerialise(SerialiserType &ser, SamplerDescriptor &el) SIZE_CHECK(72); } +template +void DoSerialise(SerialiserType &ser, DescriptorAccess &el) +{ + SERIALISE_MEMBER(stage); + SERIALISE_MEMBER(type); + SERIALISE_MEMBER(index); + SERIALISE_MEMBER(arrayElement); + SERIALISE_MEMBER(descriptorStore); + SERIALISE_MEMBER(byteOffset); + SERIALISE_MEMBER(byteSize); + SERIALISE_MEMBER(staticallyUnused); + + SIZE_CHECK(32); +} + template void DoSerialise(SerialiserType &ser, StencilFace &el) { @@ -2564,6 +2579,7 @@ INSTANTIATE_SERIALISE_TYPE(DebugPixelInputs) INSTANTIATE_SERIALISE_TYPE(DescriptorRange) INSTANTIATE_SERIALISE_TYPE(Descriptor) INSTANTIATE_SERIALISE_TYPE(SamplerDescriptor) +INSTANTIATE_SERIALISE_TYPE(DescriptorAccess) INSTANTIATE_SERIALISE_TYPE(D3D11Pipe::Layout) INSTANTIATE_SERIALISE_TYPE(D3D11Pipe::InputAssembly) INSTANTIATE_SERIALISE_TYPE(D3D11Pipe::View) diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 8e544b7cc..e806e70c2 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -131,6 +131,13 @@ rdcarray ReplayController::GetDescriptors(ResourceId descriptorStore return m_pDevice->GetDescriptors(m_pDevice->GetLiveID(descriptorStore), ranges); } +rdcarray ReplayController::GetDescriptorAccess() +{ + CHECK_REPLAY_THREAD(); + + return m_pDevice->GetDescriptorAccess(); +} + rdcarray ReplayController::GetSamplerDescriptors( ResourceId descriptorStore, const rdcarray &ranges) { diff --git a/renderdoc/replay/replay_controller.h b/renderdoc/replay/replay_controller.h index 21298f9e6..5553c4bb0 100644 --- a/renderdoc/replay/replay_controller.h +++ b/renderdoc/replay/replay_controller.h @@ -154,6 +154,7 @@ public: const rdcarray &ranges); rdcarray GetSamplerDescriptors(ResourceId descriptorStore, const rdcarray &ranges); + rdcarray GetDescriptorAccess(); rdcarray GetDisassemblyTargets(bool withPipeline); rdcstr DisassembleShader(ResourceId pipeline, const ShaderReflection *refl, const rdcstr &target); diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index e4e2e9d13..730748ada 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -166,6 +166,7 @@ public: const rdcarray &ranges) = 0; virtual rdcarray GetSamplerDescriptors( ResourceId descriptorStore, const rdcarray &ranges) = 0; + virtual rdcarray GetDescriptorAccess() = 0; virtual FrameRecord GetFrameRecord() = 0;