blob: b283a3355e289398e74c0deb5e6242ec54f62f88 [file] [log] [blame]
// 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 <cmath>
#include <memory>
#include <string>
#include <vector>
#include "dawn/common/Constants.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderBundleEncoderDescriptor.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
#include "webgpu/webgpu_glfw.h"
#include "GLFW/glfw3.h"
namespace dawn::utils {
static constexpr std::array<wgpu::CompositeAlphaMode, 5> kAllAlphaModes = {
wgpu::CompositeAlphaMode::Auto, wgpu::CompositeAlphaMode::Opaque,
wgpu::CompositeAlphaMode::Premultiplied, wgpu::CompositeAlphaMode::Unpremultiplied,
wgpu::CompositeAlphaMode::Inherit,
};
static constexpr std::array<wgpu::PresentMode, 4> kAllPresentModes = {
wgpu::PresentMode::Fifo,
wgpu::PresentMode::FifoRelaxed,
wgpu::PresentMode::Immediate,
wgpu::PresentMode::Mailbox,
};
} // namespace dawn::utils
namespace dawn {
namespace {
struct GLFWindowDestroyer {
void operator()(GLFWwindow* ptr) { glfwDestroyWindow(ptr); }
};
class SurfaceConfigurationValidationTests : public DawnTest {
public:
void SetUp() override {
DawnTest::SetUp();
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
glfwSetErrorCallback([](int code, const char* message) {
ErrorLog() << "GLFW error " << code << " " << message;
});
// GLFW can fail to start in headless environments, in which SwapChainTests are
// inapplicable. Skip this cases without producing a test failure.
if (glfwInit() == GLFW_FALSE) {
GTEST_SKIP();
}
// Set GLFW_NO_API to avoid GLFW bringing up a GL context that we won't use.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window.reset(glfwCreateWindow(500, 400, "SurfaceConfigurationValidationTests window",
nullptr, nullptr));
int width;
int height;
glfwGetFramebufferSize(window.get(), &width, &height);
baseConfig.device = device;
baseConfig.width = width;
baseConfig.height = height;
baseConfig.usage = wgpu::TextureUsage::RenderAttachment;
baseConfig.viewFormatCount = 0;
baseConfig.viewFormats = nullptr;
}
wgpu::Surface CreateTestSurface() {
return wgpu::glfw::CreateSurfaceForWindow(GetInstance(), window.get());
}
wgpu::SurfaceConfiguration GetPreferredConfiguration(wgpu::Surface surface) {
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
wgpu::SurfaceConfiguration config = baseConfig;
config.format = capabilities.formats[0];
config.alphaMode = capabilities.alphaModes[0];
config.presentMode = capabilities.presentModes[0];
return config;
}
bool SupportsFormat(const wgpu::SurfaceCapabilities& capabilities, wgpu::TextureFormat format) {
for (size_t i = 0; i < capabilities.formatCount; ++i) {
if (capabilities.formats[i] == format) {
return true;
}
}
return false;
}
bool SupportsAlphaMode(const wgpu::SurfaceCapabilities& capabilities,
wgpu::CompositeAlphaMode mode) {
if (mode == wgpu::CompositeAlphaMode::Auto) {
return true;
}
for (size_t i = 0; i < capabilities.alphaModeCount; ++i) {
if (capabilities.alphaModes[i] == mode) {
return true;
}
}
return false;
}
bool SupportsPresentMode(const wgpu::SurfaceCapabilities& capabilities,
wgpu::PresentMode mode) {
for (size_t i = 0; i < capabilities.presentModeCount; ++i) {
if (capabilities.presentModes[i] == mode) {
return true;
}
}
return false;
}
protected:
std::unique_ptr<GLFWwindow, GLFWindowDestroyer> window = nullptr;
wgpu::SurfaceConfiguration baseConfig;
};
// Using undefined format is not valid
TEST_P(SurfaceConfigurationValidationTests, UndefinedFormat) {
wgpu::SurfaceConfiguration config;
config.device = device;
config.format = wgpu::TextureFormat::Undefined;
ASSERT_DEVICE_ERROR(CreateTestSurface().Configure(&config));
}
// Supports at least one configuration
TEST_P(SurfaceConfigurationValidationTests, AtLeastOneSupportedConfiguration) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
ASSERT_GT(capabilities.formatCount, 0u);
ASSERT_GT(capabilities.alphaModeCount, 0u);
ASSERT_GT(capabilities.presentModeCount, 0u);
}
// AlphaMode::Auto should never be reported but always supported.
TEST_P(SurfaceConfigurationValidationTests, AlphaModeAuto) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
// Auto is never reported.
for (size_t i = 0; i < capabilities.alphaModeCount; ++i) {
ASSERT_NE(capabilities.alphaModes[i], wgpu::CompositeAlphaMode::Auto);
}
// But always supported.
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.alphaMode = wgpu::CompositeAlphaMode::Auto;
surface.Configure(&config);
}
// Using any combination of the reported capability is ok for configuring the surface.
TEST_P(SurfaceConfigurationValidationTests, AnyCombinationOfCapabilities) {
// TODO(dawn:2320): Fails with "internal drawable creation failed" on the Windows NVIDIA CQ
// builders but not locally. This is a similar limitation to SwapChainTests.SwitchPresentMode.
DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsNvidia());
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceConfiguration config = baseConfig;
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
for (wgpu::TextureFormat format : dawn::utils::kAllTextureFormats) {
for (wgpu::CompositeAlphaMode alphaMode : dawn::utils::kAllAlphaModes) {
for (wgpu::PresentMode presentMode : dawn::utils::kAllPresentModes) {
config.format = format;
config.alphaMode = alphaMode;
config.presentMode = presentMode;
if (!SupportsFormat(capabilities, config.format) ||
!SupportsAlphaMode(capabilities, config.alphaMode) ||
!SupportsPresentMode(capabilities, config.presentMode)) {
ASSERT_DEVICE_ERROR(surface.Configure(&config));
} else {
surface.Configure(&config);
// Check that we can present
wgpu::SurfaceTexture surfaceTexture;
surface.GetCurrentTexture(&surfaceTexture);
surface.Present();
}
device.Tick();
}
}
}
}
// Invalid view format fails
TEST_P(SurfaceConfigurationValidationTests, InvalidViewFormat) {
wgpu::Surface surface = CreateTestSurface();
auto invalid = wgpu::TextureFormat::R32Uint;
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.viewFormatCount = 1;
config.viewFormats = &invalid;
ASSERT_DEVICE_ERROR(surface.Configure(&config));
}
// View format is valid when it matches the config format
TEST_P(SurfaceConfigurationValidationTests, ValidViewFormat) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.viewFormatCount = 1;
config.viewFormats = &config.format;
surface.Configure(&config);
// TODO(dawn:2320): Also test the equivalent (non-)sRGB view format
}
// A width of 0 fails
TEST_P(SurfaceConfigurationValidationTests, ZeroWidth) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.width = 0;
ASSERT_DEVICE_ERROR(surface.Configure(&config));
}
// A height of 0 fails
TEST_P(SurfaceConfigurationValidationTests, ZeroHeight) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.height = 0;
ASSERT_DEVICE_ERROR(surface.Configure(&config));
}
// A width that exceeds the maximum texture size fails
TEST_P(SurfaceConfigurationValidationTests, ExcessiveWidth) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SupportedLimits supported;
device.GetLimits(&supported);
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.width = supported.limits.maxTextureDimension1D + 1;
ASSERT_DEVICE_ERROR(surface.Configure(&config));
}
// A height that exceeds the maximum texture size fails
TEST_P(SurfaceConfigurationValidationTests, ExcessiveHeight) {
wgpu::Surface surface = CreateTestSurface();
wgpu::SupportedLimits supported;
device.GetLimits(&supported);
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.height = supported.limits.maxTextureDimension2D + 1;
ASSERT_DEVICE_ERROR(surface.Configure(&config));
}
// A surface that was not configured must not be unconfigured
TEST_P(SurfaceConfigurationValidationTests, UnconfigureNonConfiguredSurfaceFails) {
// TODO(dawn:2320): With SwiftShader, this throws a device error anyways (maybe because
// mInstance->ConsumedError calls the device error callback?). We should have a
// ASSERT_INSTANCE_ERROR to fully fix this test case.
DAWN_SUPPRESS_TEST_IF(IsSwiftshader());
// TODO(dawn:2320): This cannot throw a device error since the surface is
// not aware of the device at this stage.
/*ASSERT_DEVICE_ERROR(*/ CreateTestSurface().Unconfigure() /*)*/;
}
// Test that including unsupported usage flag will result in error.
TEST_P(SurfaceConfigurationValidationTests, ErrorIncludeUnsupportedUsage) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceCapabilities caps;
surface.GetCapabilities(adapter, &caps);
// Assuming StorageAttachment is not supported.
DAWN_TEST_UNSUPPORTED_IF(caps.usages & wgpu::TextureUsage::StorageAttachment);
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.usage = wgpu::TextureUsage::StorageAttachment;
ASSERT_DEVICE_ERROR_MSG(surface.Configure(&config), testing::HasSubstr("Usages requested"));
}
// Test that validation of format capabilities still happens.
TEST_P(SurfaceConfigurationValidationTests, StorageRequiresCapableFormat) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
wgpu::Surface surface = CreateTestSurface();
wgpu::SurfaceCapabilities caps;
surface.GetCapabilities(adapter, &caps);
// Assuming StorageAttachment is not supported.
DAWN_TEST_UNSUPPORTED_IF(!(caps.usages & wgpu::TextureUsage::StorageBinding));
for (uint32_t i = 0; i < caps.formatCount; i++) {
wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
config.usage = wgpu::TextureUsage::StorageBinding;
config.format = caps.formats[i];
if (utils::TextureFormatSupportsStorageTexture(config.format, device, false)) {
surface.Configure(&config);
} else {
ASSERT_DEVICE_ERROR_MSG(surface.Configure(&config),
testing::HasSubstr("TextureUsage::StorageBinding"));
}
}
}
DAWN_INSTANTIATE_TEST(SurfaceConfigurationValidationTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
NullBackend(),
VulkanBackend());
} // anonymous namespace
} // namespace dawn