dawn-cmake/src/dawn/tests/end2end/SwapChainTests.cpp

405 lines
15 KiB
C++
Raw Normal View History

// Copyright 2020 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.
#include <vector>
#include "dawn/common/Constants.h"
#include "dawn/common/Log.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
#include "webgpu/webgpu_glfw.h"
#include "GLFW/glfw3.h"
namespace dawn {
namespace {
class SwapChainTests : public DawnTest {
public:
void SetUp() override {
DawnTest::SetUp();
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
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 = glfwCreateWindow(400, 400, "SwapChainValidationTests window", nullptr, nullptr);
int width;
int height;
glfwGetFramebufferSize(window, &width, &height);
surface = wgpu::glfw::CreateSurfaceForWindow(GetInstance(), window);
ASSERT_NE(surface, nullptr);
baseDescriptor.width = width;
baseDescriptor.height = height;
baseDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
baseDescriptor.format = wgpu::TextureFormat::BGRA8Unorm;
baseDescriptor.presentMode = wgpu::PresentMode::Mailbox;
}
void TearDown() override {
// Destroy the surface before the window as required by webgpu-native.
surface = wgpu::Surface();
if (window != nullptr) {
glfwDestroyWindow(window);
}
DawnTest::TearDown();
}
void ClearTexture(wgpu::TextureView view, wgpu::Color color) {
utils::ComboRenderPassDescriptor desc({view});
desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
desc.cColorAttachments[0].clearValue = color;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
}
protected:
GLFWwindow* window = nullptr;
wgpu::Surface surface;
wgpu::SwapChainDescriptor baseDescriptor;
};
// Basic test for creating a swapchain and presenting one frame.
TEST_P(SwapChainTests, Basic) {
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
swapchain.Present();
}
// Test replacing the swapchain
TEST_P(SwapChainTests, ReplaceBasic) {
wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain1.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
swapchain1.Present();
wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain2.GetCurrentTextureView(), {0.0, 1.0, 0.0, 1.0});
swapchain2.Present();
}
// Test replacing the swapchain after GetCurrentTextureView
TEST_P(SwapChainTests, ReplaceAfterGet) {
wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain1.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain2.GetCurrentTextureView(), {0.0, 1.0, 0.0, 1.0});
swapchain2.Present();
}
// Test destroying the swapchain after GetCurrentTextureView
TEST_P(SwapChainTests, DestroyAfterGet) {
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
}
// Test destroying the surface before the swapchain
TEST_P(SwapChainTests, DestroySurface) {
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
surface = nullptr;
}
// Test destroying the surface before the swapchain but after GetCurrentTextureView
TEST_P(SwapChainTests, DestroySurfaceAfterGet) {
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
ClearTexture(swapchain.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
surface = nullptr;
}
// Test switching between present modes.
TEST_P(SwapChainTests, SwitchPresentMode) {
Make Surface reference its attached SwapChain This solves an issues where when switching swapchains the previous one was destroyed before the new one was created, doing so detached itself from the Surface, which in turn made the new swapchain not do a graceful transition via vkSwapchainCreateInfoKHR::oldSwapchain. Keeping the reference on the surface makes sure we always have knowledge of the previous swapchain when replacing it. It requires re-working the lifetime model of NewSwapChainBase to not require a call to DetachFromSurface in the destructor, and having the Device explicitly tell a swapchain it got attached on creation (otherwise there are ASSERTs firing when swapchain creation fails). In addition, backends are changed to use a SwapChain::Create method and fail with a validation error (for now) when the previous swapchain didn't use the same API. vulkan::SwapChain is updated to use the previous swapchain's device's fenced deleter to destroy it which is important in the device switching tests. The SwapChainValidationTests are updated because with the lifetime changes the texture view can be kept alive after the application has lost the last reference to the wgpu::SwapChain. TBRing since it was reviewed in a different CL (but for the wrong branch). TBR=enga@chromium.org TBR=senorblanco@chromium.org Bug: dawn:269 Change-Id: Ie4374b5685af990d68969ab9cd7767e53c287ace Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/31041 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
2020-10-27 04:31:26 -07:00
// Fails with "internal drawable creation failed" on the Windows NVIDIA CQ builders but not
// locally.
DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsNvidia());
// TODO(jiawei.shao@intel.com): find out why this test sometimes hangs on the latest Linux Intel
// Vulkan drivers.
DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
constexpr wgpu::PresentMode kAllPresentModes[] = {
wgpu::PresentMode::Immediate,
wgpu::PresentMode::Fifo,
wgpu::PresentMode::Mailbox,
};
for (wgpu::PresentMode mode1 : kAllPresentModes) {
for (wgpu::PresentMode mode2 : kAllPresentModes) {
wgpu::SwapChainDescriptor desc = baseDescriptor;
desc.presentMode = mode1;
wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &desc);
ClearTexture(swapchain1.GetCurrentTextureView(), {0.0, 0.0, 0.0, 1.0});
swapchain1.Present();
desc.presentMode = mode2;
wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &desc);
ClearTexture(swapchain2.GetCurrentTextureView(), {0.0, 0.0, 0.0, 1.0});
swapchain2.Present();
}
}
}
// Test resizing the swapchain and without resizing the window.
TEST_P(SwapChainTests, ResizingSwapChainOnly) {
for (int i = 0; i < 10; i++) {
wgpu::SwapChainDescriptor desc = baseDescriptor;
desc.width += i * 10;
desc.height -= i * 10;
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
ClearTexture(swapchain.GetCurrentTextureView(), {0.05f * i, 0.0f, 0.0f, 1.0f});
swapchain.Present();
}
}
// Test resizing the window but not the swapchain.
TEST_P(SwapChainTests, ResizingWindowOnly) {
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
for (int i = 0; i < 10; i++) {
glfwSetWindowSize(window, 400 - 10 * i, 400 + 10 * i);
glfwPollEvents();
ClearTexture(swapchain.GetCurrentTextureView(), {0.05f * i, 0.0f, 0.0f, 1.0f});
swapchain.Present();
}
}
// Test resizing both the window and the swapchain at the same time.
TEST_P(SwapChainTests, ResizingWindowAndSwapChain) {
// TODO(crbug.com/dawn/1205) Currently failing on new NVIDIA GTX 1660s on Linux/Vulkan.
DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsNvidia());
for (int i = 0; i < 10; i++) {
glfwSetWindowSize(window, 400 - 10 * i, 400 + 10 * i);
glfwPollEvents();
int width;
int height;
glfwGetFramebufferSize(window, &width, &height);
wgpu::SwapChainDescriptor desc = baseDescriptor;
desc.width = width;
desc.height = height;
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
ClearTexture(swapchain.GetCurrentTextureView(), {0.05f * i, 0.0f, 0.0f, 1.0f});
swapchain.Present();
}
}
// Test switching devices on the same adapter.
TEST_P(SwapChainTests, SwitchingDevice) {
wgpu::Device device2 = CreateDevice();
for (int i = 0; i < 3; i++) {
wgpu::Device deviceToUse;
if (i % 2 == 0) {
deviceToUse = device;
} else {
deviceToUse = device2;
}
wgpu::SwapChain swapchain = deviceToUse.CreateSwapChain(surface, &baseDescriptor);
swapchain.GetCurrentTextureView();
swapchain.Present();
}
}
// Test that calling Device.GetSupportedSurfaceUsage() will throw an error because
// SurfaceCapabilities is not enabled.
TEST_P(SwapChainTests, ErrorGetSurfaceSupportedUsage) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
EXPECT_FALSE(device.HasFeature(wgpu::FeatureName::SurfaceCapabilities));
ASSERT_DEVICE_ERROR_MSG(
{
auto usageFlags = device.GetSupportedSurfaceUsage(surface);
EXPECT_EQ(usageFlags, wgpu::TextureUsage::None);
},
testing::HasSubstr("FeatureName::SurfaceCapabilities is not enabled"));
}
// Test that creating swapchain with TextureBinding usage without enabling SurfaceCapabilities
// feature should fail.
TEST_P(SwapChainTests, ErrorCreateWithTextureBindingUsage) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
EXPECT_FALSE(device.HasFeature(wgpu::FeatureName::SurfaceCapabilities));
auto desc = baseDescriptor;
desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
ASSERT_DEVICE_ERROR_MSG(
{ auto swapchain = device.CreateSwapChain(surface, &desc); },
testing::HasSubstr("require enabling FeatureName::SurfaceCapabilities"));
}
class SwapChainWithAdditionalUsageTests : public SwapChainTests {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
std::vector<wgpu::FeatureName> features;
if (!UsesWire() && SupportsFeatures({wgpu::FeatureName::SurfaceCapabilities})) {
features.push_back(wgpu::FeatureName::SurfaceCapabilities);
}
return features;
}
void SetUp() override {
SwapChainTests::SetUp();
// If parent class skipped the test, we should skip as well.
if (surface == nullptr) {
GTEST_SKIP();
}
DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::SurfaceCapabilities}));
}
void SampleTexture(wgpu::TextureView view,
uint32_t width,
uint32_t height,
utils::RGBA8 expectedColor) {
wgpu::TextureDescriptor texDescriptor;
texDescriptor.size = {width, height, 1};
texDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
texDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::CopyDst;
texDescriptor.mipLevelCount = 1;
texDescriptor.sampleCount = 1;
wgpu::Texture dstTexture = device.CreateTexture(&texDescriptor);
wgpu::TextureView dstView = dstTexture.CreateView();
// Create a render pipeline to blit |view| into |dstView|.
utils::ComboRenderPipelineDescriptor pipelineDesc;
pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
var pos = array(
vec2f(-1.0, -1.0),
vec2f(-1.0, 1.0),
vec2f( 1.0, -1.0),
vec2f(-1.0, 1.0),
vec2f( 1.0, -1.0),
vec2f( 1.0, 1.0));
return vec4f(pos[VertexIndex], 0.0, 1.0);
}
)");
pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var texture : texture_2d<f32>;
@fragment
fn main(@builtin(position) coord: vec4f) -> @location(0) vec4f {
return textureLoad(texture, vec2i(coord.xy), 0);
}
)");
pipelineDesc.cTargets[0].format = texDescriptor.format;
// Submit a render pass to perform the blit from |view| to |dstView|.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
wgpu::BindGroup bindGroup =
utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}});
utils::ComboRenderPassDescriptor renderPassInfo({dstView});
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(6);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_TEXTURE_EQ(expectedColor, dstTexture, {0, 0});
EXPECT_TEXTURE_EQ(expectedColor, dstTexture, {width - 1, height - 1});
}
};
TEST_P(SwapChainWithAdditionalUsageTests, GetSurfaceSupportedUsage) {
auto usageFlags = device.GetSupportedSurfaceUsage(surface);
EXPECT_NE(usageFlags, wgpu::TextureUsage::None);
}
// Test that sampling from swapchain is supported.
TEST_P(SwapChainWithAdditionalUsageTests, SamplingFromSwapChain) {
// TODO(dawn:1551): Reenable on D3D11 after suppressing the D3D11 debug layer warning for
// setting the same private data multiple times.
DAWN_SUPPRESS_TEST_IF(IsD3D11());
// Skip all tests if readable surface doesn't support texture binding
DAWN_TEST_UNSUPPORTED_IF(
(device.GetSupportedSurfaceUsage(surface) & wgpu::TextureUsage::TextureBinding) == 0);
auto desc = baseDescriptor;
desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
ClearTexture(swapchain.GetCurrentTextureView(), {1.0, 0.0, 0.0, 1.0});
SampleTexture(swapchain.GetCurrentTextureView(), baseDescriptor.width, baseDescriptor.height,
utils::RGBA8::kRed);
swapchain.Present();
}
// Test that including unsupported usage flag will result in error.
TEST_P(SwapChainWithAdditionalUsageTests, ErrorIncludeUnsupportedUsage) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
auto supportedUsage = device.GetSupportedSurfaceUsage(surface);
// Assuming StorageBinding is not supported.
DAWN_TEST_UNSUPPORTED_IF((supportedUsage & wgpu::TextureUsage::StorageBinding) != 0);
auto desc = baseDescriptor;
desc.usage = supportedUsage | wgpu::TextureUsage::StorageBinding;
ASSERT_DEVICE_ERROR_MSG({ auto swapchain = device.CreateSwapChain(surface, &desc); },
testing::HasSubstr("is not supported"));
}
DAWN_INSTANTIATE_TEST(SwapChainTests, MetalBackend(), VulkanBackend());
DAWN_INSTANTIATE_TEST(SwapChainWithAdditionalUsageTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
NullBackend(),
VulkanBackend());
} // anonymous namespace
} // namespace dawn