|  | // Copyright 2024 The Dawn & Tint Authors | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | // 1. Redistributions of source code must retain the above copyright notice, this | 
|  | //    list of conditions and the following disclaimer. | 
|  | // | 
|  | // 2. Redistributions in binary form must reproduce the above copyright notice, | 
|  | //    this list of conditions and the following disclaimer in the documentation | 
|  | //    and/or other materials provided with the distribution. | 
|  | // | 
|  | // 3. Neither the name of the copyright holder nor the names of its | 
|  | //    contributors may be used to endorse or promote products derived from | 
|  | //    this software without specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
|  | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | #include <dawn/webgpu_cpp_print.h> | 
|  | #include <emscripten.h> | 
|  | #include <gmock/gmock.h> | 
|  | #include <gtest/gtest.h> | 
|  | #include <webgpu/webgpu_cpp.h> | 
|  |  | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using testing::_; | 
|  | using testing::HasSubstr; | 
|  |  | 
|  | class InstanceLevelTests : public testing::Test { | 
|  | public: | 
|  | void SetUp() override { | 
|  | wgpu::InstanceDescriptor descriptor = {}; | 
|  | // The unit tests use wgpuInstanceWaitAny(WGPUFuture, timeoutNS) with timeoutNS > 0 | 
|  | // which requires the `timedWaitAnyEnable` property enabled on the instance capability. | 
|  | descriptor.capabilities.timedWaitAnyEnable = true; | 
|  | instance = wgpu::CreateInstance(&descriptor); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | wgpu::Adapter RequestAdapter(const wgpu::RequestAdapterOptions* adapterOptions = nullptr) { | 
|  | // TODO(crbug.com/404535888): Remove this sleep once we figure out regression. | 
|  | static bool sSleepWorkaround = false; | 
|  | if (!sSleepWorkaround) { | 
|  | // Make the test sleep for an additional 2 seconds before trying to requestAdapter. | 
|  | emscripten_sleep(2000); | 
|  | sSleepWorkaround = true; | 
|  | } | 
|  |  | 
|  | wgpu::RequestAdapterStatus status; | 
|  | wgpu::Adapter result = nullptr; | 
|  | EXPECT_EQ(instance.WaitAny( | 
|  | instance.RequestAdapter( | 
|  | adapterOptions, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&status, &result](wgpu::RequestAdapterStatus s, wgpu::Adapter adapter, | 
|  | wgpu::StringView message) { | 
|  | status = s; | 
|  | result = std::move(adapter); | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(status, wgpu::RequestAdapterStatus::Success); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | wgpu::Instance instance; | 
|  | }; | 
|  |  | 
|  | TEST_F(InstanceLevelTests, RequestAdapter) { | 
|  | EXPECT_NE(RequestAdapter(), nullptr); | 
|  | } | 
|  |  | 
|  | class AdapterLevelTests : public InstanceLevelTests { | 
|  | public: | 
|  | void SetUp() override { | 
|  | InstanceLevelTests::SetUp(); | 
|  | adapter = RequestAdapter(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | wgpu::Device RequestDevice(const wgpu::DeviceDescriptor* descriptor = nullptr) { | 
|  | wgpu::RequestDeviceStatus status; | 
|  | wgpu::Device result = nullptr; | 
|  | EXPECT_EQ( | 
|  | instance.WaitAny(adapter.RequestDevice( | 
|  | descriptor, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&status, &result](wgpu::RequestDeviceStatus s, | 
|  | wgpu::Device device, wgpu::StringView message) { | 
|  | status = s; | 
|  | result = std::move(device); | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(status, wgpu::RequestDeviceStatus::Success); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | wgpu::Adapter adapter; | 
|  | }; | 
|  |  | 
|  | TEST_F(AdapterLevelTests, RequestDevice) { | 
|  | EXPECT_NE(RequestDevice(), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(AdapterLevelTests, RequestDeviceThenDestroy) { | 
|  | wgpu::Device device = nullptr; | 
|  | wgpu::DeviceLostReason reason; | 
|  |  | 
|  | wgpu::DeviceDescriptor descriptor = {}; | 
|  | descriptor.SetDeviceLostCallback( | 
|  | wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&device, &reason](const wgpu::Device& d, wgpu::DeviceLostReason r, wgpu::StringView) { | 
|  | reason = r; | 
|  | EXPECT_EQ(device.Get(), d.Get()); | 
|  | }); | 
|  | device = RequestDevice(&descriptor); | 
|  |  | 
|  | auto deviceLostFuture = device.GetLostFuture(); | 
|  | device.Destroy(); | 
|  | ASSERT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed); | 
|  | } | 
|  |  | 
|  | TEST_F(AdapterLevelTests, RequestDeviceThenDrop) { | 
|  | wgpu::DeviceLostReason reason; | 
|  |  | 
|  | wgpu::DeviceDescriptor descriptor = {}; | 
|  | descriptor.SetDeviceLostCallback( | 
|  | wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&reason](const wgpu::Device&, wgpu::DeviceLostReason r, wgpu::StringView) { reason = r; }); | 
|  | wgpu::Device device = RequestDevice(&descriptor); | 
|  |  | 
|  | auto deviceLostFuture = device.GetLostFuture(); | 
|  | device = nullptr; | 
|  | ASSERT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed); | 
|  | } | 
|  |  | 
|  | class DeviceLevelTests : public AdapterLevelTests { | 
|  | public: | 
|  | void SetUp() override { | 
|  | AdapterLevelTests::SetUp(); | 
|  |  | 
|  | wgpu::DeviceDescriptor descriptor = {}; | 
|  | descriptor.SetDeviceLostCallback( | 
|  | wgpu::CallbackMode::AllowSpontaneous, | 
|  | [](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView) { | 
|  | EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed); | 
|  | }); | 
|  | descriptor.SetUncapturedErrorCallback( | 
|  | [](const wgpu::Device& d, wgpu::ErrorType t, wgpu::StringView m, | 
|  | DeviceLevelTests* self) { self->uncapturedErrorCb.Call(d, t, m); }, | 
|  | this); | 
|  | device = RequestDevice(&descriptor); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // For teardown, we explicitly wait for the device lost so that we can ensure that errors | 
|  | // have been flushed. | 
|  | auto deviceLostFuture = device.GetLostFuture(); | 
|  | device = nullptr; | 
|  | EXPECT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | wgpu::ShaderModule CreateShaderModule(const char* source) { | 
|  | wgpu::ShaderSourceWGSL wgsl; | 
|  | wgsl.code = source; | 
|  | wgpu::ShaderModuleDescriptor desc; | 
|  | desc.nextInChain = &wgsl; | 
|  | return device.CreateShaderModule(&desc); | 
|  | } | 
|  |  | 
|  | wgpu::Device device; | 
|  |  | 
|  | // Mock callback used for uncaptured errors so that test writers can add expectations on this | 
|  | // callback which will enforce the expectations at teardown of the test. | 
|  | testing::StrictMock< | 
|  | testing::MockFunction<void(const wgpu::Device&, wgpu::ErrorType, wgpu::StringView)>> | 
|  | uncapturedErrorCb; | 
|  | }; | 
|  |  | 
|  | TEST_F(DeviceLevelTests, ValidationError) { | 
|  | EXPECT_CALL(uncapturedErrorCb, Call(_, wgpu::ErrorType::Validation, _)).Times(1); | 
|  |  | 
|  | wgpu::BufferDescriptor desc = {}; | 
|  | desc.size = 1024; | 
|  | desc.usage = static_cast<wgpu::BufferUsage>(UINT64_MAX); | 
|  | wgpu::Buffer buffer = device.CreateBuffer(&desc); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceLevelTests, PopErrorScope) { | 
|  | device.PushErrorScope(wgpu::ErrorFilter::Validation); | 
|  |  | 
|  | wgpu::BufferDescriptor desc = {}; | 
|  | desc.size = 1024; | 
|  | desc.usage = static_cast<wgpu::BufferUsage>(UINT64_MAX); | 
|  | wgpu::Buffer buffer = device.CreateBuffer(&desc); | 
|  |  | 
|  | wgpu::PopErrorScopeStatus status; | 
|  | wgpu::ErrorType type; | 
|  | EXPECT_EQ(instance.WaitAny( | 
|  | device.PopErrorScope(wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&status, &type](wgpu::PopErrorScopeStatus s, | 
|  | wgpu::ErrorType t, wgpu::StringView) { | 
|  | status = s; | 
|  | type = t; | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(status, wgpu::PopErrorScopeStatus::Success); | 
|  | EXPECT_EQ(type, wgpu::ErrorType::Validation); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceLevelTests, BufferMapAndWorkDone) { | 
|  | static constexpr uint32_t kData = 100u; | 
|  | size_t kSize = sizeof(uint32_t); | 
|  |  | 
|  | wgpu::Buffer src; | 
|  | wgpu::Buffer dst; | 
|  | { | 
|  | wgpu::BufferDescriptor desc; | 
|  | desc.label = "src"; | 
|  | desc.size = kSize; | 
|  | desc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite; | 
|  | src = device.CreateBuffer(&desc); | 
|  | } | 
|  | { | 
|  | wgpu::BufferDescriptor desc; | 
|  | desc.label = "dst"; | 
|  | desc.size = kSize; | 
|  | desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead; | 
|  | dst = device.CreateBuffer(&desc); | 
|  | } | 
|  |  | 
|  | // Map the writable buffer and write to it. | 
|  | wgpu::MapAsyncStatus writeStatus; | 
|  | EXPECT_EQ(instance.WaitAny( | 
|  | src.MapAsync(wgpu::MapMode::Write, 0, kSize, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&writeStatus](wgpu::MapAsyncStatus status, wgpu::StringView) { | 
|  | writeStatus = status; | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | ASSERT_EQ(writeStatus, wgpu::MapAsyncStatus::Success); | 
|  | auto writeData = static_cast<uint32_t*>(src.GetMappedRange()); | 
|  | ASSERT_NE(writeData, nullptr); | 
|  | *writeData = kData; | 
|  | src.Unmap(); | 
|  |  | 
|  | // Copy the buffer to the readable one, and wait for the copy to complete. Note that the wait | 
|  | // for the copy is not strictly necessary since the map async call following it will already | 
|  | // wait for it, but we do it explicitly here to test the additional entry point. | 
|  | wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | 
|  | encoder.CopyBufferToBuffer(src, 0, dst, 0, kSize); | 
|  | wgpu::CommandBuffer commands = encoder.Finish(); | 
|  | wgpu::Queue queue = device.GetQueue(); | 
|  | queue.Submit(1, &commands); | 
|  |  | 
|  | wgpu::QueueWorkDoneStatus copyStatus; | 
|  | EXPECT_EQ( | 
|  | instance.WaitAny(queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowSpontaneous, | 
|  | [©Status](wgpu::QueueWorkDoneStatus status) { | 
|  | copyStatus = status; | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | ASSERT_EQ(copyStatus, wgpu::QueueWorkDoneStatus::Success); | 
|  |  | 
|  | // Map the readable buffer and verify the contents. | 
|  | wgpu::MapAsyncStatus readStatus; | 
|  | EXPECT_EQ(instance.WaitAny( | 
|  | dst.MapAsync(wgpu::MapMode::Read, 0, kSize, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&readStatus](wgpu::MapAsyncStatus status, wgpu::StringView) { | 
|  | readStatus = status; | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | ASSERT_EQ(readStatus, wgpu::MapAsyncStatus::Success); | 
|  | auto readData = static_cast<const uint32_t*>(dst.GetConstMappedRange()); | 
|  | ASSERT_NE(readData, nullptr); | 
|  | EXPECT_EQ(*readData, kData); | 
|  | dst.Unmap(); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceLevelTests, CreateComputePipelineAsync) { | 
|  | wgpu::ComputePipelineDescriptor desc; | 
|  | desc.compute.module = CreateShaderModule(R"( | 
|  | @compute @workgroup_size(1) fn main() {} | 
|  | )"); | 
|  |  | 
|  | wgpu::CreatePipelineAsyncStatus status; | 
|  | wgpu::ComputePipeline pipeline = nullptr; | 
|  | EXPECT_EQ(instance.WaitAny(device.CreateComputePipelineAsync( | 
|  | &desc, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&status, &pipeline](wgpu::CreatePipelineAsyncStatus s, | 
|  | wgpu::ComputePipeline p, wgpu::StringView) { | 
|  | status = s; | 
|  | pipeline = std::move(p); | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(status, wgpu::CreatePipelineAsyncStatus::Success); | 
|  | EXPECT_NE(pipeline, nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceLevelTests, CreateRenderPipelineAsync) { | 
|  | wgpu::RenderPipelineDescriptor desc; | 
|  | desc.vertex.module = CreateShaderModule(R"( | 
|  | @vertex fn main() -> @builtin(position) vec4f { | 
|  | return vec4f(0.0, 0.0, 0.0, 1.0); | 
|  | } | 
|  | )"); | 
|  |  | 
|  | wgpu::FragmentState frag; | 
|  | frag.module = CreateShaderModule(R"( | 
|  | @fragment fn main() -> @location(0) vec4f { | 
|  | return vec4f(0.0, 1.0, 0.0, 1.0); | 
|  | } | 
|  | )"); | 
|  | wgpu::ColorTargetState target; | 
|  | target.format = wgpu::TextureFormat::RGBA8Unorm; | 
|  | frag.targetCount = 1; | 
|  | frag.targets = ⌖ | 
|  | desc.fragment = &frag; | 
|  |  | 
|  | wgpu::CreatePipelineAsyncStatus status; | 
|  | wgpu::RenderPipeline pipeline = nullptr; | 
|  | EXPECT_EQ(instance.WaitAny(device.CreateRenderPipelineAsync( | 
|  | &desc, wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&status, &pipeline](wgpu::CreatePipelineAsyncStatus s, | 
|  | wgpu::RenderPipeline p, wgpu::StringView) { | 
|  | status = s; | 
|  | pipeline = std::move(p); | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(status, wgpu::CreatePipelineAsyncStatus::Success); | 
|  | EXPECT_NE(pipeline, nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceLevelTests, GetCompilationInfo) { | 
|  | wgpu::ShaderModule shader = CreateShaderModule(R"( | 
|  | @fragment fn main(@location(0) x : f32) { | 
|  | return; | 
|  | return; | 
|  | } | 
|  | )"); | 
|  |  | 
|  | wgpu::CompilationMessageType messageType; | 
|  | std::string message; | 
|  | bool hasUtf16 = false; | 
|  | EXPECT_EQ(instance.WaitAny( | 
|  | shader.GetCompilationInfo( | 
|  | wgpu::CallbackMode::AllowSpontaneous, | 
|  | [&message, &messageType, &hasUtf16](wgpu::CompilationInfoRequestStatus s, | 
|  | const wgpu::CompilationInfo* info) { | 
|  | ASSERT_EQ(s, wgpu::CompilationInfoRequestStatus::Success); | 
|  | ASSERT_NE(info, nullptr); | 
|  | ASSERT_EQ(info->messageCount, 1); | 
|  |  | 
|  | message = info->messages[0].message; | 
|  | messageType = info->messages[0].type; | 
|  |  | 
|  | size_t chainLength = 0; | 
|  | for (const auto* chain = info->messages[0].nextInChain; chain != nullptr; | 
|  | chain = chain->nextInChain) { | 
|  | if (chain->sType == wgpu::SType::DawnCompilationMessageUtf16) { | 
|  | hasUtf16 = true; | 
|  | } | 
|  | chainLength++; | 
|  | } | 
|  | ASSERT_EQ(chainLength, 1); | 
|  | }), | 
|  | UINT64_MAX), | 
|  | wgpu::WaitStatus::Success); | 
|  | EXPECT_EQ(messageType, wgpu::CompilationMessageType::Warning); | 
|  | EXPECT_TRUE(hasUtf16); | 
|  | EXPECT_THAT(message, HasSubstr("unreachable")); | 
|  | } | 
|  |  | 
|  | }  // namespace |