// Copyright 2017 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef SRC_DAWN_NATIVE_DEVICE_H_ #define SRC_DAWN_NATIVE_DEVICE_H_ #include #include #include #include #include #include #include "dawn/native/BlobCache.h" #include "dawn/native/CacheKey.h" #include "dawn/native/Commands.h" #include "dawn/native/ComputePipeline.h" #include "dawn/native/Error.h" #include "dawn/native/Features.h" #include "dawn/native/Format.h" #include "dawn/native/Forward.h" #include "dawn/native/Limits.h" #include "dawn/native/ObjectBase.h" #include "dawn/native/ObjectType_autogen.h" #include "dawn/native/RefCountedWithExternalCount.h" #include "dawn/native/StagingBuffer.h" #include "dawn/native/Toggles.h" #include "dawn/native/DawnNative.h" #include "dawn/native/dawn_platform.h" namespace dawn::platform { class WorkerTaskPool; } // namespace dawn::platform namespace dawn::native { class AsyncTaskManager; class AttachmentState; class AttachmentStateBlueprint; class CallbackTaskManager; class DynamicUploader; class ErrorScopeStack; class OwnedCompilationMessages; struct CallbackTask; struct InternalPipelineStore; struct ShaderModuleParseResult; using WGSLExtensionSet = std::unordered_set; class DeviceBase : public RefCountedWithExternalCount { public: DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor); ~DeviceBase() override; void HandleError(InternalErrorType type, const char* message); bool ConsumedError(MaybeError maybeError) { if (DAWN_UNLIKELY(maybeError.IsError())) { ConsumeError(maybeError.AcquireError()); return true; } return false; } template bool ConsumedError(ResultOrError resultOrError, T* result) { if (DAWN_UNLIKELY(resultOrError.IsError())) { ConsumeError(resultOrError.AcquireError()); return true; } *result = resultOrError.AcquireSuccess(); return false; } template bool ConsumedError(MaybeError maybeError, const char* formatStr, const Args&... args) { if (DAWN_UNLIKELY(maybeError.IsError())) { std::unique_ptr error = maybeError.AcquireError(); if (error->GetType() == InternalErrorType::Validation) { std::string out; absl::UntypedFormatSpec format(formatStr); if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) { error->AppendContext(std::move(out)); } else { error->AppendContext( absl::StrFormat("[Failed to format error: \"%s\"]", formatStr)); } } ConsumeError(std::move(error)); return true; } return false; } template bool ConsumedError(ResultOrError resultOrError, T* result, const char* formatStr, const Args&... args) { if (DAWN_UNLIKELY(resultOrError.IsError())) { std::unique_ptr error = resultOrError.AcquireError(); if (error->GetType() == InternalErrorType::Validation) { std::string out; absl::UntypedFormatSpec format(formatStr); if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) { error->AppendContext(std::move(out)); } else { error->AppendContext( absl::StrFormat("[Failed to format error: \"%s\"]", formatStr)); } } ConsumeError(std::move(error)); return true; } *result = resultOrError.AcquireSuccess(); return false; } MaybeError ValidateObject(const ApiObjectBase* object) const; AdapterBase* GetAdapter() const; dawn::platform::Platform* GetPlatform() const; // Returns the Format corresponding to the wgpu::TextureFormat or an error if the format // isn't a valid wgpu::TextureFormat or isn't supported by this device. // The pointer returned has the same lifetime as the device. ResultOrError GetInternalFormat(wgpu::TextureFormat format) const; // Returns the Format corresponding to the wgpu::TextureFormat and assumes the format is // valid and supported. // The reference returned has the same lifetime as the device. const Format& GetValidInternalFormat(wgpu::TextureFormat format) const; const Format& GetValidInternalFormat(FormatIndex formatIndex) const; virtual ResultOrError> CreateCommandBuffer( CommandEncoder* encoder, const CommandBufferDescriptor* descriptor) = 0; ExecutionSerial GetCompletedCommandSerial() const; ExecutionSerial GetLastSubmittedCommandSerial() const; ExecutionSerial GetFutureSerial() const; ExecutionSerial GetPendingCommandSerial() const; // Many Dawn objects are completely immutable once created which means that if two // creations are given the same arguments, they can return the same object. Reusing // objects will help make comparisons between objects by a single pointer comparison. // // Technically no object is immutable as they have a reference count, and an // application with reference-counting issues could "see" that objects are reused. // This is solved by automatic-reference counting, and also the fact that when using // the client-server wire every creation will get a different proxy object, with a // different reference count. // // When trying to create an object, we give both the descriptor and an example of what // the created object will be, the "blueprint". The blueprint is just a FooBase object // instead of a backend Foo object. If the blueprint doesn't match an object in the // cache, then the descriptor is used to make a new object. ResultOrError> GetOrCreateBindGroupLayout( const BindGroupLayoutDescriptor* descriptor, PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0)); void UncacheBindGroupLayout(BindGroupLayoutBase* obj); BindGroupLayoutBase* GetEmptyBindGroupLayout(); void UncacheComputePipeline(ComputePipelineBase* obj); ResultOrError> GetOrCreatePlaceholderTextureViewForExternalTexture(); ResultOrError> GetOrCreatePipelineLayout( const PipelineLayoutDescriptor* descriptor); void UncachePipelineLayout(PipelineLayoutBase* obj); void UncacheRenderPipeline(RenderPipelineBase* obj); ResultOrError> GetOrCreateSampler(const SamplerDescriptor* descriptor); void UncacheSampler(SamplerBase* obj); ResultOrError> GetOrCreateShaderModule( const ShaderModuleDescriptor* descriptor, ShaderModuleParseResult* parseResult, OwnedCompilationMessages* compilationMessages); void UncacheShaderModule(ShaderModuleBase* obj); Ref GetOrCreateAttachmentState(AttachmentStateBlueprint* blueprint); Ref GetOrCreateAttachmentState( const RenderBundleEncoderDescriptor* descriptor); Ref GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor); Ref GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor); void UncacheAttachmentState(AttachmentState* obj); Ref GetOrCreatePipelineCache(const CacheKey& key); // Object creation methods that be used in a reentrant manner. ResultOrError> CreateBindGroup(const BindGroupDescriptor* descriptor); ResultOrError> CreateBindGroupLayout( const BindGroupLayoutDescriptor* descriptor, bool allowInternalBinding = false); ResultOrError> CreateBuffer(const BufferDescriptor* descriptor); ResultOrError> CreateCommandEncoder( const CommandEncoderDescriptor* descriptor = nullptr); ResultOrError> CreateComputePipeline( const ComputePipelineDescriptor* descriptor); MaybeError CreateComputePipelineAsync(const ComputePipelineDescriptor* descriptor, WGPUCreateComputePipelineAsyncCallback callback, void* userdata); ResultOrError> CreatePipelineLayout( const PipelineLayoutDescriptor* descriptor); ResultOrError> CreateQuerySet(const QuerySetDescriptor* descriptor); ResultOrError> CreateRenderBundleEncoder( const RenderBundleEncoderDescriptor* descriptor); ResultOrError> CreateRenderPipeline( const RenderPipelineDescriptor* descriptor); MaybeError CreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata); ResultOrError> CreateSampler(const SamplerDescriptor* descriptor = nullptr); ResultOrError> CreateShaderModule( const ShaderModuleDescriptor* descriptor, OwnedCompilationMessages* compilationMessages = nullptr); ResultOrError> CreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor); ResultOrError> CreateTexture(const TextureDescriptor* descriptor); ResultOrError> CreateTextureView(TextureBase* texture, const TextureViewDescriptor* descriptor); // Implementation of API object creation methods. DO NOT use them in a reentrant manner. BindGroupBase* APICreateBindGroup(const BindGroupDescriptor* descriptor); BindGroupLayoutBase* APICreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor); BufferBase* APICreateBuffer(const BufferDescriptor* descriptor); CommandEncoder* APICreateCommandEncoder(const CommandEncoderDescriptor* descriptor); ComputePipelineBase* APICreateComputePipeline(const ComputePipelineDescriptor* descriptor); PipelineLayoutBase* APICreatePipelineLayout(const PipelineLayoutDescriptor* descriptor); QuerySetBase* APICreateQuerySet(const QuerySetDescriptor* descriptor); void APICreateComputePipelineAsync(const ComputePipelineDescriptor* descriptor, WGPUCreateComputePipelineAsyncCallback callback, void* userdata); void APICreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata); RenderBundleEncoder* APICreateRenderBundleEncoder( const RenderBundleEncoderDescriptor* descriptor); RenderPipelineBase* APICreateRenderPipeline(const RenderPipelineDescriptor* descriptor); ExternalTextureBase* APICreateExternalTexture(const ExternalTextureDescriptor* descriptor); SamplerBase* APICreateSampler(const SamplerDescriptor* descriptor); ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor); SwapChainBase* APICreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor); TextureBase* APICreateTexture(const TextureDescriptor* descriptor); InternalPipelineStore* GetInternalPipelineStore(); // For Dawn Wire BufferBase* APICreateErrorBuffer(); QueueBase* APIGetQueue(); bool APIGetLimits(SupportedLimits* limits) const; // Note that we should not use this function to query the features which can only be enabled // behind toggles (use IsFeatureEnabled() instead). bool APIHasFeature(wgpu::FeatureName feature) const; // Note that we should not use this function to query the features which can only be enabled // behind toggles (use IsFeatureEnabled() instead). size_t APIEnumerateFeatures(wgpu::FeatureName* features) const; void APIInjectError(wgpu::ErrorType type, const char* message); bool APITick(); void APISetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata); void APISetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata); void APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata); void APIPushErrorScope(wgpu::ErrorFilter filter); bool APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata); MaybeError ValidateIsAlive() const; BlobCache* GetBlobCache(); CachedBlob LoadCachedBlob(const CacheKey& key); void StoreCachedBlob(const CacheKey& key, const CachedBlob& blob); virtual ResultOrError> CreateStagingBuffer(size_t size) = 0; virtual MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, uint64_t sourceOffset, BufferBase* destination, uint64_t destinationOffset, uint64_t size) = 0; virtual MaybeError CopyFromStagingToTexture(const StagingBufferBase* source, const TextureDataLayout& src, TextureCopy* dst, const Extent3D& copySizePixels) = 0; DynamicUploader* GetDynamicUploader() const; // The device state which is a combination of creation state and loss state. // // - BeingCreated: the device didn't finish creation yet and the frontend cannot be used // (both for the application calling WebGPU, or re-entrant calls). No work exists on // the GPU timeline. // - Alive: the device is usable and might have work happening on the GPU timeline. // - BeingDisconnected: the device is no longer usable because we are waiting for all // work on the GPU timeline to finish. (this is to make validation prevent the // application from adding more work during the transition from Available to // Disconnected) // - Disconnected: there is no longer work happening on the GPU timeline and the CPU data // structures can be safely destroyed without additional synchronization. // - Destroyed: the device is disconnected and resources have been reclaimed. enum class State { BeingCreated, Alive, BeingDisconnected, Disconnected, Destroyed, }; State GetState() const; bool IsLost() const; void TrackObject(ApiObjectBase* object); std::mutex* GetObjectListMutex(ObjectType type); std::vector GetTogglesUsed() const; WGSLExtensionSet GetWGSLExtensionAllowList() const; bool IsToggleEnabled(Toggle toggle) const; bool IsValidationEnabled() const; bool IsRobustnessEnabled() const; size_t GetLazyClearCountForTesting(); void IncrementLazyClearCountForTesting(); size_t GetDeprecationWarningCountForTesting(); void EmitDeprecationWarning(const char* warning); void EmitLog(const char* message); void EmitLog(WGPULoggingType loggingType, const char* message); void APILoseForTesting(); QueueBase* GetQueue() const; // AddFutureSerial is used to update the mFutureSerial with the max serial needed to be // ticked in order to clean up all pending callback work or to execute asynchronous resource // writes. It should be given the serial that a callback is tracked with, so that once that // serial is completed, it can be resolved and cleaned up. This is so that when there is no // gpu work (the last submitted serial has not moved beyond the completed serial), Tick can // still check if we have pending work to take care of, rather than hanging and never // reaching the serial the work will be executed on. void AddFutureSerial(ExecutionSerial serial); // Check for passed fences and set the new completed serial MaybeError CheckPassedSerials(); MaybeError Tick(); // TODO(crbug.com/dawn/839): Organize the below backend-specific parameters into the struct // BackendMetadata that we can query from the device. virtual uint32_t GetOptimalBytesPerRowAlignment() const = 0; virtual uint64_t GetOptimalBufferToTextureCopyOffsetAlignment() const = 0; virtual float GetTimestampPeriodInNS() const = 0; virtual bool ShouldDuplicateNumWorkgroupsForDispatchIndirect( ComputePipelineBase* computePipeline) const; virtual bool MayRequireDuplicationOfIndirectParameters() const; virtual bool ShouldDuplicateParametersForDrawIndirect( const RenderPipelineBase* renderPipelineBase) const; // TODO(crbug.com/dawn/1434): Make this function non-overridable when we support requesting // Adapter with toggles. virtual bool IsFeatureEnabled(Feature feature) const; const CombinedLimits& GetLimits() const; AsyncTaskManager* GetAsyncTaskManager() const; CallbackTaskManager* GetCallbackTaskManager() const; dawn::platform::WorkerTaskPool* GetWorkerTaskPool() const; void AddComputePipelineAsyncCallbackTask(Ref pipeline, std::string errorMessage, WGPUCreateComputePipelineAsyncCallback callback, void* userdata); void AddRenderPipelineAsyncCallbackTask(Ref pipeline, std::string errorMessage, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata); PipelineCompatibilityToken GetNextPipelineCompatibilityToken(); const CacheKey& GetCacheKey() const; const std::string& GetLabel() const; void APISetLabel(const char* label); void APIDestroy(); virtual void AppendDebugLayerMessages(ErrorData* error) {} protected: // Constructor used only for mocking and testing. DeviceBase(); void SetToggle(Toggle toggle, bool isEnabled); void ForceSetToggle(Toggle toggle, bool isEnabled); MaybeError Initialize(Ref defaultQueue); void DestroyObjects(); void Destroy(); // Incrememt mLastSubmittedSerial when we submit the next serial void IncrementLastSubmittedCommandSerial(); private: void WillDropLastExternalRef() override; virtual ResultOrError> CreateBindGroupImpl( const BindGroupDescriptor* descriptor) = 0; virtual ResultOrError> CreateBindGroupLayoutImpl( const BindGroupLayoutDescriptor* descriptor, PipelineCompatibilityToken pipelineCompatibilityToken) = 0; virtual ResultOrError> CreateBufferImpl(const BufferDescriptor* descriptor) = 0; virtual ResultOrError> CreateExternalTextureImpl( const ExternalTextureDescriptor* descriptor); virtual ResultOrError> CreatePipelineLayoutImpl( const PipelineLayoutDescriptor* descriptor) = 0; virtual ResultOrError> CreateQuerySetImpl( const QuerySetDescriptor* descriptor) = 0; virtual ResultOrError> CreateSamplerImpl( const SamplerDescriptor* descriptor) = 0; virtual ResultOrError> CreateShaderModuleImpl( const ShaderModuleDescriptor* descriptor, ShaderModuleParseResult* parseResult, OwnedCompilationMessages* compilationMessages) = 0; virtual ResultOrError> CreateSwapChainImpl( const SwapChainDescriptor* descriptor) = 0; // Note that previousSwapChain may be nullptr, or come from a different backend. virtual ResultOrError> CreateSwapChainImpl( Surface* surface, NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) = 0; virtual ResultOrError> CreateTextureImpl( const TextureDescriptor* descriptor) = 0; virtual ResultOrError> CreateTextureViewImpl( TextureBase* texture, const TextureViewDescriptor* descriptor) = 0; virtual Ref CreateUninitializedComputePipelineImpl( const ComputePipelineDescriptor* descriptor) = 0; virtual Ref CreateUninitializedRenderPipelineImpl( const RenderPipelineDescriptor* descriptor) = 0; virtual void SetLabelImpl(); virtual MaybeError TickImpl() = 0; void FlushCallbackTaskQueue(); ResultOrError> CreateEmptyBindGroupLayout(); Ref GetCachedComputePipeline( ComputePipelineBase* uninitializedComputePipeline); Ref GetCachedRenderPipeline( RenderPipelineBase* uninitializedRenderPipeline); Ref AddOrGetCachedComputePipeline( Ref computePipeline); Ref AddOrGetCachedRenderPipeline(Ref renderPipeline); virtual Ref GetOrCreatePipelineCacheImpl(const CacheKey& key); virtual void InitializeComputePipelineAsyncImpl(Ref computePipeline, WGPUCreateComputePipelineAsyncCallback callback, void* userdata); virtual void InitializeRenderPipelineAsyncImpl(Ref renderPipeline, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata); void ApplyToggleOverrides(const DawnTogglesDeviceDescriptor* togglesDescriptor); void ApplyFeatures(const DeviceDescriptor* deviceDescriptor); void SetDefaultToggles(); void SetWGSLExtensionAllowList(); void ConsumeError(std::unique_ptr error); // Each backend should implement to check their passed fences if there are any and return a // completed serial. Return 0 should indicate no fences to check. virtual ResultOrError CheckAndUpdateCompletedSerials() = 0; // During shut down of device, some operations might have been started since the last submit // and waiting on a serial that doesn't have a corresponding fence enqueued. Fake serials to // make all commands look completed. void AssumeCommandsComplete(); bool IsDeviceIdle(); // mCompletedSerial tracks the last completed command serial that the fence has returned. // mLastSubmittedSerial tracks the last submitted command serial. // During device removal, the serials could be artificially incremented // to make it appear as if commands have been compeleted. They can also be artificially // incremented when no work is being done in the GPU so CPU operations don't have to wait on // stale serials. // mFutureSerial tracks the largest serial we need to tick to for asynchronous commands or // callbacks to fire ExecutionSerial mCompletedSerial = ExecutionSerial(0); ExecutionSerial mLastSubmittedSerial = ExecutionSerial(0); ExecutionSerial mFutureSerial = ExecutionSerial(0); // DestroyImpl is used to clean up and release resources used by device, does not wait for // GPU or check errors. virtual void DestroyImpl() = 0; // WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for // destruction. This is only used when properly destructing the device. For a real // device loss, this function doesn't need to be called since the driver already closed all // resources. virtual MaybeError WaitForIdleForDestruction() = 0; wgpu::ErrorCallback mUncapturedErrorCallback = nullptr; void* mUncapturedErrorUserdata = nullptr; wgpu::LoggingCallback mLoggingCallback = nullptr; void* mLoggingUserdata = nullptr; wgpu::DeviceLostCallback mDeviceLostCallback = nullptr; void* mDeviceLostUserdata = nullptr; std::unique_ptr mErrorScopeStack; // The Device keeps a ref to the Instance so that any live Device keeps the Instance alive. // The Instance shouldn't need to ref child objects so this shouldn't introduce ref cycles. // The Device keeps a simple pointer to the Adapter because the Adapter is owned by the // Instance. Ref mInstance; AdapterBase* mAdapter = nullptr; // The object caches aren't exposed in the header as they would require a lot of // additional includes. struct Caches; std::unique_ptr mCaches; Ref mEmptyBindGroupLayout; Ref mExternalTexturePlaceholderView; std::unique_ptr mDynamicUploader; std::unique_ptr mAsyncTaskManager; Ref mQueue; struct DeprecationWarnings; std::unique_ptr mDeprecationWarnings; State mState = State::BeingCreated; // Encompasses the mutex and the actual list that contains all live objects "owned" by the // device. struct ApiObjectList { std::mutex mutex; LinkedList objects; }; PerObjectType mObjectLists; FormatTable mFormatTable; TogglesSet mEnabledToggles; TogglesSet mOverridenToggles; size_t mLazyClearCountForTesting = 0; std::atomic_uint64_t mNextPipelineCompatibilityToken; CombinedLimits mLimits; FeaturesSet mEnabledFeatures; WGSLExtensionSet mWGSLExtensionAllowList; std::unique_ptr mInternalPipelineStore; std::unique_ptr mCallbackTaskManager; std::unique_ptr mWorkerTaskPool; std::string mLabel; CacheKey mDeviceCacheKey; }; } // namespace dawn::native #endif // SRC_DAWN_NATIVE_DEVICE_H_