mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-13 19:01:24 +00:00
Bug: tint:1382 Change-Id: I58ad2c88fde1e7c96f2ae8257e6df924b94b61db Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/77660 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com>
358 lines
14 KiB
C++
358 lines
14 KiB
C++
// 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 "tests/DawnTest.h"
|
|
|
|
#include "common/Constants.h"
|
|
#include "common/Log.h"
|
|
#include "utils/ComboRenderPipelineDescriptor.h"
|
|
#include "utils/GLFWUtils.h"
|
|
#include "utils/WGPUHelpers.h"
|
|
|
|
#include "GLFW/glfw3.h"
|
|
|
|
class SwapChainValidationTests : 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) {
|
|
dawn::ErrorLog() << "GLFW error " << code << " " << message;
|
|
});
|
|
DAWN_TEST_UNSUPPORTED_IF(!glfwInit());
|
|
|
|
// The SwapChainValidationTests don't create devices so we don't need to call
|
|
// SetupGLFWWindowHintsForBackend. Set GLFW_NO_API anyway to avoid GLFW bringing up a GL
|
|
// context that we won't use.
|
|
ASSERT_TRUE(!IsOpenGL());
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
window = glfwCreateWindow(400, 400, "SwapChainValidationTests window", nullptr, nullptr);
|
|
|
|
surface = utils::CreateSurfaceForWindow(GetInstance(), window);
|
|
ASSERT_NE(surface, nullptr);
|
|
|
|
goodDescriptor.width = 1;
|
|
goodDescriptor.height = 1;
|
|
goodDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
|
|
goodDescriptor.format = wgpu::TextureFormat::BGRA8Unorm;
|
|
goodDescriptor.presentMode = wgpu::PresentMode::Mailbox;
|
|
|
|
badDescriptor = goodDescriptor;
|
|
badDescriptor.width = 0;
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Destroy the surface before the window as required by webgpu-native.
|
|
surface = wgpu::Surface();
|
|
if (window != nullptr) {
|
|
glfwDestroyWindow(window);
|
|
}
|
|
DawnTest::TearDown();
|
|
}
|
|
|
|
protected:
|
|
GLFWwindow* window = nullptr;
|
|
wgpu::Surface surface;
|
|
wgpu::SwapChainDescriptor goodDescriptor;
|
|
wgpu::SwapChainDescriptor badDescriptor;
|
|
|
|
// Checks that a RenderAttachment view is an error by trying to create a render pass on it.
|
|
void CheckTextureViewIsError(wgpu::TextureView view) {
|
|
CheckTextureView(view, true, false);
|
|
}
|
|
|
|
// Checks that a RenderAttachment view is an error by trying to submit a render pass on it.
|
|
void CheckTextureViewIsDestroyed(wgpu::TextureView view) {
|
|
CheckTextureView(view, false, true);
|
|
}
|
|
|
|
// Checks that a RenderAttachment view is valid by submitting a render pass on it.
|
|
void CheckTextureViewIsValid(wgpu::TextureView view) {
|
|
CheckTextureView(view, false, false);
|
|
}
|
|
|
|
private:
|
|
void CheckTextureView(wgpu::TextureView view, bool errorAtFinish, bool errorAtSubmit) {
|
|
utils::ComboRenderPassDescriptor renderPassDesc({view});
|
|
|
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
|
|
pass.EndPass();
|
|
|
|
if (errorAtFinish) {
|
|
ASSERT_DEVICE_ERROR(encoder.Finish());
|
|
} else {
|
|
wgpu::CommandBuffer commands = encoder.Finish();
|
|
|
|
if (errorAtSubmit) {
|
|
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
|
|
} else {
|
|
queue.Submit(1, &commands);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Control case for a successful swapchain creation and presenting.
|
|
TEST_P(SwapChainValidationTests, CreationSuccess) {
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
swapchain.Present();
|
|
}
|
|
|
|
// Checks that the creation size must be a valid 2D texture size.
|
|
TEST_P(SwapChainValidationTests, InvalidCreationSize) {
|
|
wgpu::Limits supportedLimits = GetSupportedLimits().limits;
|
|
// A width of 0 is invalid.
|
|
{
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.width = 0;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
// A height of 0 is invalid.
|
|
{
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.height = 0;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
|
|
// A width of maxTextureDimension2D is valid but maxTextureDimension2D + 1 isn't.
|
|
{
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.width = supportedLimits.maxTextureDimension2D;
|
|
device.CreateSwapChain(surface, &desc);
|
|
|
|
desc.width = supportedLimits.maxTextureDimension2D + 1;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
|
|
// A height of maxTextureDimension2D is valid but maxTextureDimension2D + 1 isn't.
|
|
{
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.height = supportedLimits.maxTextureDimension2D;
|
|
device.CreateSwapChain(surface, &desc);
|
|
|
|
desc.height = supportedLimits.maxTextureDimension2D + 1;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
}
|
|
|
|
// Checks that the creation usage must be RenderAttachment
|
|
TEST_P(SwapChainValidationTests, InvalidCreationUsage) {
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.usage = wgpu::TextureUsage::TextureBinding;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
|
|
// Checks that the creation format must (currently) be BGRA8Unorm
|
|
TEST_P(SwapChainValidationTests, InvalidCreationFormat) {
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.format = wgpu::TextureFormat::RGBA8Unorm;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
|
|
// Checks that the implementation must be zero.
|
|
TEST_P(SwapChainValidationTests, InvalidWithImplementation) {
|
|
wgpu::SwapChainDescriptor desc = goodDescriptor;
|
|
desc.implementation = 1;
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
|
|
}
|
|
|
|
// Check swapchain operations with an error swapchain are errors
|
|
TEST_P(SwapChainValidationTests, OperationsOnErrorSwapChain) {
|
|
wgpu::SwapChain swapchain;
|
|
ASSERT_DEVICE_ERROR(swapchain = device.CreateSwapChain(surface, &badDescriptor));
|
|
|
|
wgpu::TextureView view;
|
|
ASSERT_DEVICE_ERROR(view = swapchain.GetCurrentTextureView());
|
|
CheckTextureViewIsError(view);
|
|
|
|
ASSERT_DEVICE_ERROR(swapchain.Present());
|
|
}
|
|
|
|
// Check it is invalid to call present without getting a current view.
|
|
TEST_P(SwapChainValidationTests, PresentWithoutCurrentView) {
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
|
|
// Check it is invalid if we never called GetCurrentTextureView
|
|
ASSERT_DEVICE_ERROR(swapchain.Present());
|
|
|
|
// Check it is invalid if we never called since the last present.
|
|
swapchain.GetCurrentTextureView();
|
|
swapchain.Present();
|
|
ASSERT_DEVICE_ERROR(swapchain.Present());
|
|
}
|
|
|
|
// Check that the current view isn't destroyed when the ref to the swapchain is lost because the
|
|
// swapchain is kept alive by the surface. Also check after we lose all refs to the surface, the
|
|
// texture is destroyed.
|
|
TEST_P(SwapChainValidationTests, ViewValidAfterSwapChainRefLost) {
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
|
|
swapchain = nullptr;
|
|
CheckTextureViewIsValid(view);
|
|
|
|
surface = nullptr;
|
|
CheckTextureViewIsDestroyed(view);
|
|
}
|
|
|
|
// Check that the current view is the destroyed state after present.
|
|
TEST_P(SwapChainValidationTests, ViewDestroyedAfterPresent) {
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
swapchain.Present();
|
|
|
|
CheckTextureViewIsDestroyed(view);
|
|
}
|
|
|
|
// Check that returned view is of the current format / usage / dimension / size / sample count
|
|
TEST_P(SwapChainValidationTests, ReturnedViewCharacteristics) {
|
|
utils::ComboRenderPipelineDescriptor pipelineDesc;
|
|
pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
|
|
@stage(vertex) fn main() -> @builtin(position) vec4<f32> {
|
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
})");
|
|
pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
|
|
struct FragmentOut {
|
|
@location(0) target0 : vec4<f32>;
|
|
@location(1) target1 : f32;
|
|
};
|
|
@stage(fragment) fn main() -> FragmentOut {
|
|
var out : FragmentOut;
|
|
out.target0 = vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
|
out.target1 = 0.5;
|
|
return out;
|
|
})");
|
|
// Validation will check that the sample count of the view matches this format.
|
|
pipelineDesc.multisample.count = 1;
|
|
pipelineDesc.cFragment.targetCount = 2;
|
|
// Validation will check that the format of the view matches this format.
|
|
pipelineDesc.cTargets[0].format = wgpu::TextureFormat::BGRA8Unorm;
|
|
pipelineDesc.cTargets[1].format = wgpu::TextureFormat::R8Unorm;
|
|
device.CreateRenderPipeline(&pipelineDesc);
|
|
|
|
// Create a second texture to be used as render pass attachment. Validation will check that the
|
|
// size of the view matches the size of this texture.
|
|
wgpu::TextureDescriptor textureDesc;
|
|
textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
|
|
textureDesc.dimension = wgpu::TextureDimension::e2D;
|
|
textureDesc.size = {1, 1, 1};
|
|
textureDesc.format = wgpu::TextureFormat::R8Unorm;
|
|
textureDesc.sampleCount = 1;
|
|
wgpu::Texture secondTexture = device.CreateTexture(&textureDesc);
|
|
|
|
// Get the swapchain view and try to use it in the render pass to trigger all the validation.
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
|
|
// Validation will also check the dimension of the view is 2D, and it's usage contains
|
|
// RenderAttachment
|
|
utils::ComboRenderPassDescriptor renderPassDesc({view, secondTexture.CreateView()});
|
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
|
|
pass.EndPass();
|
|
wgpu::CommandBuffer commands = encoder.Finish();
|
|
|
|
queue.Submit(1, &commands);
|
|
|
|
// Check that view doesn't have extra formats like Sampled.
|
|
// TODO(cwallez@chromium.org): also check for [Readonly]Storage once that's implemented.
|
|
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
|
|
device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}});
|
|
ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}}));
|
|
}
|
|
|
|
// Check that failing to create a new swapchain doesn't replace the previous one.
|
|
TEST_P(SwapChainValidationTests, ErrorSwapChainDoesntReplacePreviousOne) {
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &badDescriptor));
|
|
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
swapchain.Present();
|
|
}
|
|
|
|
// Check that after replacement, all swapchain operations are errors and the view is destroyed.
|
|
TEST_P(SwapChainValidationTests, ReplacedSwapChainIsInvalid) {
|
|
{
|
|
wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
device.CreateSwapChain(surface, &goodDescriptor);
|
|
ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView());
|
|
}
|
|
|
|
{
|
|
wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView();
|
|
device.CreateSwapChain(surface, &goodDescriptor);
|
|
|
|
CheckTextureViewIsDestroyed(view);
|
|
ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
|
|
}
|
|
}
|
|
|
|
// Check that after surface destruction, all swapchain operations are errors and the view is
|
|
// destroyed. The test is split in two to reset the wgpu::Surface in the middle.
|
|
TEST_P(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_GetView) {
|
|
wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
surface = nullptr;
|
|
ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView());
|
|
}
|
|
TEST_P(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_AfterGetView) {
|
|
wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView();
|
|
surface = nullptr;
|
|
|
|
CheckTextureViewIsDestroyed(view);
|
|
ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
|
|
}
|
|
|
|
// Test that after Device is Lost, all swap chain operations fail
|
|
static void ToMockDeviceLostCallback(WGPUDeviceLostReason reason,
|
|
const char* message,
|
|
void* userdata) {
|
|
DawnTest* self = static_cast<DawnTest*>(userdata);
|
|
self->StartExpectDeviceError();
|
|
}
|
|
|
|
// Test that new swap chain present fails after device is lost
|
|
TEST_P(SwapChainValidationTests, NewSwapChainPresentFailsAfterDeviceLost) {
|
|
device.SetDeviceLostCallback(ToMockDeviceLostCallback, this);
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
wgpu::TextureView view = swapchain.GetCurrentTextureView();
|
|
|
|
device.LoseForTesting();
|
|
ASSERT_DEVICE_ERROR(swapchain.Present());
|
|
}
|
|
|
|
// Test that new swap chain get current texture view fails after device is lost
|
|
TEST_P(SwapChainValidationTests, NewSwapChainGetCurrentTextureViewFailsAfterDevLost) {
|
|
device.SetDeviceLostCallback(ToMockDeviceLostCallback, this);
|
|
wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
|
|
|
|
device.LoseForTesting();
|
|
ASSERT_DEVICE_ERROR(swapchain.GetCurrentTextureView());
|
|
}
|
|
|
|
// Test that creation of a new swapchain fails after device is lost
|
|
TEST_P(SwapChainValidationTests, CreateNewSwapChainFailsAfterDevLost) {
|
|
device.SetDeviceLostCallback(ToMockDeviceLostCallback, this);
|
|
device.LoseForTesting();
|
|
|
|
ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &goodDescriptor));
|
|
}
|
|
|
|
DAWN_INSTANTIATE_TEST(SwapChainValidationTests, MetalBackend(), NullBackend());
|