blob: 0be2ef0397e953dc6bba2595b9055aad305f5ed1 [file] [log] [blame] [edit]
// Copyright 2018 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 <string>
#include <vector>
#include "dawn/common/Constants.h"
#include "dawn/tests/unittests/validation/ValidationTest.h"
#include "dawn/utils/ComboRenderBundleEncoderDescriptor.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
class RenderPassDescriptorValidationTest : public ValidationTest {
public:
void AssertBeginRenderPassSuccess(const wgpu::RenderPassDescriptor* descriptor) {
wgpu::CommandEncoder commandEncoder = TestBeginRenderPass(descriptor);
commandEncoder.Finish();
}
void AssertBeginRenderPassError(const wgpu::RenderPassDescriptor* descriptor) {
wgpu::CommandEncoder commandEncoder = TestBeginRenderPass(descriptor);
ASSERT_DEVICE_ERROR(commandEncoder.Finish());
}
void AssertBeginRenderPassError(const wgpu::RenderPassDescriptor* descriptor,
testing::Matcher<std::string> errorMatcher) {
wgpu::CommandEncoder commandEncoder = TestBeginRenderPass(descriptor);
ASSERT_DEVICE_ERROR(commandEncoder.Finish(), errorMatcher);
}
private:
wgpu::CommandEncoder TestBeginRenderPass(const wgpu::RenderPassDescriptor* descriptor) {
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(descriptor);
renderPassEncoder.End();
return commandEncoder;
}
};
wgpu::Texture CreateTexture(wgpu::Device& device,
wgpu::TextureDimension dimension,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height,
uint32_t arrayLayerCount,
uint32_t mipLevelCount,
uint32_t sampleCount = 1,
wgpu::TextureUsage usage = wgpu::TextureUsage::RenderAttachment) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = dimension;
descriptor.size.width = width;
descriptor.size.height = height;
descriptor.size.depthOrArrayLayers = arrayLayerCount;
descriptor.sampleCount = sampleCount;
descriptor.format = format;
descriptor.mipLevelCount = mipLevelCount;
descriptor.usage = usage;
return device.CreateTexture(&descriptor);
}
wgpu::TextureView Create2DAttachment(wgpu::Device& device,
uint32_t width,
uint32_t height,
wgpu::TextureFormat format) {
wgpu::Texture texture =
CreateTexture(device, wgpu::TextureDimension::e2D, format, width, height, 1, 1);
return texture.CreateView();
}
// Using BeginRenderPass with no attachments isn't valid
TEST_F(RenderPassDescriptorValidationTest, Empty) {
utils::ComboRenderPassDescriptor renderPass({}, nullptr);
AssertBeginRenderPassError(&renderPass);
}
// A render pass with only one color or one depth attachment is ok
TEST_F(RenderPassDescriptorValidationTest, OneAttachment) {
// One color attachment
{
wgpu::TextureView color = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
utils::ComboRenderPassDescriptor renderPass({color});
AssertBeginRenderPassSuccess(&renderPass);
}
// One depth-stencil attachment
{
wgpu::TextureView depthStencil =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8);
utils::ComboRenderPassDescriptor renderPass({}, depthStencil);
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Regression test for chromium:1487788 were cached attachment states used in a pass encoder created
// from an error command encoder are not cleaned up if the device's last reference is dropped before
// the pass.
TEST_F(RenderPassDescriptorValidationTest, ErrorEncoderLingeringAttachmentState) {
utils::ComboRenderPassDescriptor descriptor(
{Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm)});
// Purposely add a bad chain to the command encoder to force an error command encoder.
wgpu::CommandEncoderDescriptor commandEncoderDesc = {};
wgpu::ChainedStruct chain = {};
commandEncoderDesc.nextInChain = &chain;
wgpu::CommandEncoder commandEncoder;
ASSERT_DEVICE_ERROR(commandEncoder = device.CreateCommandEncoder(&commandEncoderDesc));
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&descriptor);
ExpectDeviceDestruction();
device = nullptr;
}
// Test OOB color attachment indices are handled
TEST_F(RenderPassDescriptorValidationTest, ColorAttachmentOutOfBounds) {
std::array<wgpu::RenderPassColorAttachment, kMaxColorAttachments + 1> colorAttachments;
for (uint32_t i = 0; i < colorAttachments.size(); i++) {
colorAttachments[i].view = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::R8Unorm);
colorAttachments[i].resolveTarget = nullptr;
colorAttachments[i].clearValue = {0.0f, 0.0f, 0.0f, 0.0f};
colorAttachments[i].loadOp = wgpu::LoadOp::Clear;
colorAttachments[i].storeOp = wgpu::StoreOp::Store;
}
// Control case: kMaxColorAttachments is valid.
{
wgpu::RenderPassDescriptor renderPass;
renderPass.colorAttachmentCount = kMaxColorAttachments;
renderPass.colorAttachments = colorAttachments.data();
renderPass.depthStencilAttachment = nullptr;
AssertBeginRenderPassSuccess(&renderPass);
}
// Error case: kMaxColorAttachments + 1 is an error.
{
wgpu::RenderPassDescriptor renderPass;
renderPass.colorAttachmentCount = kMaxColorAttachments + 1;
renderPass.colorAttachments = colorAttachments.data();
renderPass.depthStencilAttachment = nullptr;
AssertBeginRenderPassError(&renderPass);
}
}
// Test sparse color attachment validations
TEST_F(RenderPassDescriptorValidationTest, SparseColorAttachment) {
// Having sparse color attachment is valid.
{
std::array<wgpu::RenderPassColorAttachment, 2> colorAttachments;
colorAttachments[0].view = nullptr;
colorAttachments[1].view =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
colorAttachments[1].loadOp = wgpu::LoadOp::Load;
colorAttachments[1].storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassDescriptor renderPass;
renderPass.colorAttachmentCount = colorAttachments.size();
renderPass.colorAttachments = colorAttachments.data();
renderPass.depthStencilAttachment = nullptr;
AssertBeginRenderPassSuccess(&renderPass);
}
// When all color attachments are null
{
std::array<wgpu::RenderPassColorAttachment, 2> colorAttachments;
colorAttachments[0].view = nullptr;
colorAttachments[1].view = nullptr;
// Control case: depth stencil attachment is not null is valid.
{
wgpu::TextureView depthStencilView =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8);
wgpu::RenderPassDepthStencilAttachment depthStencilAttachment;
depthStencilAttachment.view = depthStencilView;
depthStencilAttachment.depthClearValue = 1.0f;
depthStencilAttachment.stencilClearValue = 0;
depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Clear;
depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store;
depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Clear;
depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Store;
wgpu::RenderPassDescriptor renderPass;
renderPass.colorAttachmentCount = colorAttachments.size();
renderPass.colorAttachments = colorAttachments.data();
renderPass.depthStencilAttachment = &depthStencilAttachment;
AssertBeginRenderPassSuccess(&renderPass);
}
// Error case: depth stencil attachment being null is invalid.
{
wgpu::RenderPassDescriptor renderPass;
renderPass.colorAttachmentCount = colorAttachments.size();
renderPass.colorAttachments = colorAttachments.data();
renderPass.depthStencilAttachment = nullptr;
AssertBeginRenderPassError(&renderPass);
}
}
}
// Check that the render pass color attachment must have the RenderAttachment usage.
TEST_F(RenderPassDescriptorValidationTest, ColorAttachmentInvalidUsage) {
// Control case: using a texture with RenderAttachment is valid.
{
wgpu::TextureView renderView =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
utils::ComboRenderPassDescriptor renderPass({renderView});
AssertBeginRenderPassSuccess(&renderPass);
}
// Error case: using a texture with Sampled is invalid.
{
wgpu::TextureDescriptor texDesc;
texDesc.usage = wgpu::TextureUsage::TextureBinding;
texDesc.size = {1, 1, 1};
texDesc.format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::Texture sampledTex = device.CreateTexture(&texDesc);
utils::ComboRenderPassDescriptor renderPass({sampledTex.CreateView()});
AssertBeginRenderPassError(&renderPass);
}
}
// Attachments must have the same size
TEST_F(RenderPassDescriptorValidationTest, SizeMustMatch) {
wgpu::TextureView color1x1A = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::TextureView color1x1B = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::TextureView color2x2 = Create2DAttachment(device, 2, 2, wgpu::TextureFormat::RGBA8Unorm);
wgpu::TextureView depthStencil1x1 =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8);
wgpu::TextureView depthStencil2x2 =
Create2DAttachment(device, 2, 2, wgpu::TextureFormat::Depth24PlusStencil8);
// Control case: all the same size (1x1)
{
utils::ComboRenderPassDescriptor renderPass({color1x1A, color1x1B}, depthStencil1x1);
AssertBeginRenderPassSuccess(&renderPass);
}
// One of the color attachments has a different size
{
utils::ComboRenderPassDescriptor renderPass({color1x1A, color2x2});
AssertBeginRenderPassError(&renderPass);
}
// The depth stencil attachment has a different size
{
utils::ComboRenderPassDescriptor renderPass({color1x1A, color1x1B}, depthStencil2x2);
AssertBeginRenderPassError(&renderPass);
}
}
// Attachments formats must match whether they are used for color or depth-stencil
TEST_F(RenderPassDescriptorValidationTest, FormatMismatch) {
wgpu::TextureView color = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::TextureView depthStencil =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8);
// Using depth-stencil for color
{
utils::ComboRenderPassDescriptor renderPass({depthStencil});
AssertBeginRenderPassError(&renderPass);
}
// Using color for depth-stencil
{
utils::ComboRenderPassDescriptor renderPass({}, color);
AssertBeginRenderPassError(&renderPass);
}
}
// Depth and stencil storeOps can be different
TEST_F(RenderPassDescriptorValidationTest, DepthStencilStoreOpMismatch) {
constexpr uint32_t kArrayLayers = 1;
constexpr uint32_t kLevelCount = 1;
constexpr uint32_t kSize = 32;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
wgpu::Texture colorTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers, kLevelCount);
wgpu::Texture depthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
kArrayLayers, kLevelCount);
wgpu::TextureViewDescriptor descriptor;
descriptor.dimension = wgpu::TextureViewDimension::e2D;
descriptor.baseArrayLayer = 0;
descriptor.arrayLayerCount = kArrayLayers;
descriptor.baseMipLevel = 0;
descriptor.mipLevelCount = kLevelCount;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
// Base case: StoreOps match so render pass is a success
{
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
AssertBeginRenderPassSuccess(&renderPass);
}
// Base case: StoreOps match so render pass is a success
{
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Discard;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Discard;
AssertBeginRenderPassSuccess(&renderPass);
}
// StoreOps mismatch still is a success
{
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Discard;
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Currently only texture views with arrayLayerCount == 1 are allowed to be color and depth
// stencil attachments
TEST_F(RenderPassDescriptorValidationTest, TextureViewLayerCountForColorAndDepthStencil) {
constexpr uint32_t kLevelCount = 1;
constexpr uint32_t kSize = 32;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
constexpr uint32_t kArrayLayers = 10;
wgpu::Texture colorTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers, kLevelCount);
wgpu::Texture depthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
kArrayLayers, kLevelCount);
wgpu::TextureViewDescriptor baseDescriptor;
baseDescriptor.dimension = wgpu::TextureViewDimension::e2DArray;
baseDescriptor.baseArrayLayer = 0;
baseDescriptor.arrayLayerCount = kArrayLayers;
baseDescriptor.baseMipLevel = 0;
baseDescriptor.mipLevelCount = kLevelCount;
// Using 2D array texture view with arrayLayerCount > 1 is not allowed for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.arrayLayerCount = 5;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassError(&renderPass);
}
// Using 2D array texture view with arrayLayerCount > 1 is not allowed for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.arrayLayerCount = 5;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassError(&renderPass);
}
// Using 2D array texture view that covers the first layer of the texture is OK for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.baseArrayLayer = 0;
descriptor.arrayLayerCount = 1;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D array texture view that covers the first layer is OK for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.baseArrayLayer = 0;
descriptor.arrayLayerCount = 1;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D array texture view that covers the last layer is OK for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.baseArrayLayer = kArrayLayers - 1;
descriptor.arrayLayerCount = 1;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D array texture view that covers the last layer is OK for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.baseArrayLayer = kArrayLayers - 1;
descriptor.arrayLayerCount = 1;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Check that depthSlice must be set correctly for 3D color attachments and must not be set for
// non-3D color attachments.
TEST_F(RenderPassDescriptorValidationTest, TextureViewDepthSliceForColor) {
constexpr uint32_t kSize = 8;
constexpr uint32_t kDepthOrArrayLayers = 4;
constexpr uint32_t kMipLevelCounts = 4;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::Texture colorTexture3D =
CreateTexture(device, wgpu::TextureDimension::e3D, kColorFormat, kSize, kSize,
kDepthOrArrayLayers, kMipLevelCounts);
wgpu::TextureView colorView2D = Create2DAttachment(device, kSize, kSize, kColorFormat);
wgpu::TextureViewDescriptor baseDescriptor;
baseDescriptor.dimension = wgpu::TextureViewDimension::e3D;
baseDescriptor.baseArrayLayer = 0;
baseDescriptor.arrayLayerCount = 1;
baseDescriptor.baseMipLevel = 0;
baseDescriptor.mipLevelCount = 1;
// Control case: It's valid if depthSlice is set within the depth range of a 3D color
// attachment.
{
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view});
renderPass.cColorAttachments[0].depthSlice = kDepthOrArrayLayers - 1;
AssertBeginRenderPassSuccess(&renderPass);
}
// It's invalid if depthSlice is not set for a 3D color attachment.
{
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view});
AssertBeginRenderPassError(&renderPass);
}
// It's invalid if depthSlice is out of the depth range of a 3D color attachment.
{
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view});
renderPass.cColorAttachments[0].depthSlice = kDepthOrArrayLayers;
AssertBeginRenderPassError(&renderPass);
}
// It's invalid if depthSlice is out of the depth range of a 3D color attachment with non-zero
// mip level.
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.baseMipLevel = 2;
wgpu::TextureView view = colorTexture3D.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({view});
renderPass.cColorAttachments[0].depthSlice = kDepthOrArrayLayers >> 2;
AssertBeginRenderPassError(&renderPass);
}
// Control case: It's valid if depthSlice is unset for a non-3D color attachment.
{
utils::ComboRenderPassDescriptor renderPass({colorView2D});
AssertBeginRenderPassSuccess(&renderPass);
}
// It's invalid if depthSlice is set for a non-3D color attachment.
{
utils::ComboRenderPassDescriptor renderPass({colorView2D});
renderPass.cColorAttachments[0].depthSlice = 0;
AssertBeginRenderPassError(&renderPass);
}
}
// Check that the depth slices of a 3D color attachment cannot overlap in same render pass.
TEST_F(RenderPassDescriptorValidationTest, TextureViewDepthSliceOverlaps) {
constexpr uint32_t kSize = 8;
constexpr uint32_t kDepthOrArrayLayers = 2;
constexpr uint32_t kMipLevelCounts = 2;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::Texture colorTexture3D =
CreateTexture(device, wgpu::TextureDimension::e3D, kColorFormat, kSize, kSize,
kDepthOrArrayLayers, kMipLevelCounts);
wgpu::TextureViewDescriptor baseDescriptor;
baseDescriptor.dimension = wgpu::TextureViewDimension::e3D;
baseDescriptor.baseArrayLayer = 0;
baseDescriptor.arrayLayerCount = 1;
baseDescriptor.baseMipLevel = 0;
baseDescriptor.mipLevelCount = 1;
// Control case: It's valid if different depth slices of a texture are set in a render pass.
{
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view, view});
renderPass.cColorAttachments[0].depthSlice = 0;
renderPass.cColorAttachments[1].depthSlice = 1;
AssertBeginRenderPassSuccess(&renderPass);
}
// It's valid if same depth slice of different mip levels from a texture with size [1, 1, n] is
// set in a render pass.
{
wgpu::Texture texture = CreateTexture(device, wgpu::TextureDimension::e3D, kColorFormat, 1,
1, kDepthOrArrayLayers, kMipLevelCounts);
wgpu::TextureView view = texture.CreateView(&baseDescriptor);
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.baseMipLevel = 1;
wgpu::TextureView view2 = texture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({view, view2});
renderPass.cColorAttachments[0].depthSlice = 0;
renderPass.cColorAttachments[1].depthSlice = 0;
AssertBeginRenderPassSuccess(&renderPass);
}
// It's valid if same depth slice of different textures is set in a render pass.
{
wgpu::Texture otherColorTexture3D =
CreateTexture(device, wgpu::TextureDimension::e3D, kColorFormat, kSize, kSize,
kDepthOrArrayLayers, kMipLevelCounts);
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
wgpu::TextureView view2 = otherColorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view, view2});
renderPass.cColorAttachments[0].depthSlice = 0;
renderPass.cColorAttachments[1].depthSlice = 0;
AssertBeginRenderPassSuccess(&renderPass);
}
// It's invalid if same depth slice of a texture is set twice in a render pass.
{
wgpu::TextureView view = colorTexture3D.CreateView(&baseDescriptor);
utils::ComboRenderPassDescriptor renderPass({view, view});
renderPass.cColorAttachments[0].depthSlice = 0;
renderPass.cColorAttachments[1].depthSlice = 0;
AssertBeginRenderPassError(&renderPass);
}
}
// Check that the render pass depth attachment must have the RenderAttachment usage.
TEST_F(RenderPassDescriptorValidationTest, DepthAttachmentInvalidUsage) {
// Control case: using a texture with RenderAttachment is valid.
{
wgpu::TextureView renderView =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth32Float);
utils::ComboRenderPassDescriptor renderPass({}, renderView);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassSuccess(&renderPass);
}
// Error case: using a texture with Sampled is invalid.
{
wgpu::TextureDescriptor texDesc;
texDesc.usage = wgpu::TextureUsage::TextureBinding;
texDesc.size = {1, 1, 1};
texDesc.format = wgpu::TextureFormat::Depth32Float;
wgpu::Texture sampledTex = device.CreateTexture(&texDesc);
wgpu::TextureView sampledView = sampledTex.CreateView();
utils::ComboRenderPassDescriptor renderPass({}, sampledView);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassError(&renderPass);
}
}
// Only 2D texture views with mipLevelCount == 1 are allowed to be color attachments
TEST_F(RenderPassDescriptorValidationTest, TextureViewLevelCountForColorAndDepthStencil) {
constexpr uint32_t kArrayLayers = 1;
constexpr uint32_t kSize = 32;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
constexpr uint32_t kLevelCount = 4;
wgpu::Texture colorTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers, kLevelCount);
wgpu::Texture depthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
kArrayLayers, kLevelCount);
wgpu::TextureViewDescriptor baseDescriptor;
baseDescriptor.dimension = wgpu::TextureViewDimension::e2D;
baseDescriptor.baseArrayLayer = 0;
baseDescriptor.arrayLayerCount = kArrayLayers;
baseDescriptor.baseMipLevel = 0;
baseDescriptor.mipLevelCount = kLevelCount;
// Using 2D texture view with mipLevelCount > 1 is not allowed for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.mipLevelCount = 2;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassError(&renderPass);
}
// Using 2D texture view with mipLevelCount > 1 is not allowed for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.mipLevelCount = 2;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassError(&renderPass);
}
// Using 2D texture view that covers the first level of the texture is OK for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.baseMipLevel = 0;
descriptor.mipLevelCount = 1;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D texture view that covers the first level is OK for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.baseMipLevel = 0;
descriptor.mipLevelCount = 1;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D texture view that covers the last level is OK for color
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kColorFormat;
descriptor.baseMipLevel = kLevelCount - 1;
descriptor.mipLevelCount = 1;
wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
AssertBeginRenderPassSuccess(&renderPass);
}
// Using 2D texture view that covers the last level is OK for depth stencil
{
wgpu::TextureViewDescriptor descriptor = baseDescriptor;
descriptor.format = kDepthStencilFormat;
descriptor.baseMipLevel = kLevelCount - 1;
descriptor.mipLevelCount = 1;
wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
AssertBeginRenderPassSuccess(&renderPass);
}
}
// It is not allowed to set resolve target when the color attachment is non-multisampled.
TEST_F(RenderPassDescriptorValidationTest, NonMultisampledColorWithResolveTarget) {
static constexpr uint32_t kArrayLayers = 1;
static constexpr uint32_t kLevelCount = 1;
static constexpr uint32_t kSize = 32;
static constexpr uint32_t kSampleCount = 1;
static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::Texture colorTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, kSampleCount);
wgpu::Texture resolveTargetTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, kSampleCount);
wgpu::TextureView colorTextureView = colorTexture.CreateView();
wgpu::TextureView resolveTargetTextureView = resolveTargetTexture.CreateView();
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
renderPass.cColorAttachments[0].resolveTarget = resolveTargetTextureView;
AssertBeginRenderPassError(&renderPass);
}
// drawCount must not exceed maxDrawCount
TEST_F(RenderPassDescriptorValidationTest, MaxDrawCount) {
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr uint64_t kMaxDrawCount = 16;
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
@vertex fn main() -> @builtin(position) vec4f {
return vec4f(0.0, 0.0, 0.0, 1.0);
})");
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4f {
return vec4f(0.0, 1.0, 0.0, 1.0);
})");
utils::ComboRenderPipelineDescriptor pipelineDescriptor;
pipelineDescriptor.vertex.module = vsModule;
pipelineDescriptor.cFragment.module = fsModule;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
wgpu::TextureDescriptor colorTextureDescriptor;
colorTextureDescriptor.size = {1, 1};
colorTextureDescriptor.format = kColorFormat;
colorTextureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::Texture colorTexture = device.CreateTexture(&colorTextureDescriptor);
utils::ComboRenderBundleEncoderDescriptor bundleEncoderDescriptor;
bundleEncoderDescriptor.colorFormatCount = 1;
bundleEncoderDescriptor.cColorFormats[0] = kColorFormat;
wgpu::Buffer indexBuffer =
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Index, {0, 1, 2});
wgpu::Buffer indirectBuffer =
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, {3, 1, 0, 0});
wgpu::Buffer indexedIndirectBuffer =
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, {3, 1, 0, 0, 0});
wgpu::RenderPassDescriptorMaxDrawCount maxDrawCount;
maxDrawCount.maxDrawCount = kMaxDrawCount;
// Valid. drawCount is less than the default maxDrawCount.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.Draw(3);
}
renderPass.End();
encoder.Finish();
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
renderPass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndexed(3);
}
renderPass.End();
encoder.Finish();
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndirect(indirectBuffer, 0);
}
renderPass.End();
encoder.Finish();
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
renderPass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndexedIndirect(indexedIndirectBuffer, 0);
}
renderPass.End();
encoder.Finish();
}
{
wgpu::RenderBundleEncoder renderBundleEncoder =
device.CreateRenderBundleEncoder(&bundleEncoderDescriptor);
renderBundleEncoder.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderBundleEncoder.Draw(3);
}
wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish();
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.ExecuteBundles(1, &renderBundle);
renderPass.End();
encoder.Finish();
}
// Invalid. drawCount counts up with draw calls and
// it is greater than maxDrawCount.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
renderPassDescriptor.nextInChain = &maxDrawCount;
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.Draw(3);
}
renderPass.End();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
renderPassDescriptor.nextInChain = &maxDrawCount;
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
renderPass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndexed(3);
}
renderPass.End();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
renderPassDescriptor.nextInChain = &maxDrawCount;
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndirect(indirectBuffer, 0);
}
renderPass.End();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
renderPassDescriptor.nextInChain = &maxDrawCount;
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.SetPipeline(pipeline);
renderPass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderPass.DrawIndexedIndirect(indexedIndirectBuffer, 0);
}
renderPass.End();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
wgpu::RenderBundleEncoder renderBundleEncoder =
device.CreateRenderBundleEncoder(&bundleEncoderDescriptor);
renderBundleEncoder.SetPipeline(pipeline);
for (uint64_t i = 0; i <= kMaxDrawCount; i++) {
renderBundleEncoder.Draw(3);
}
wgpu::RenderBundle renderBundle = renderBundleEncoder.Finish();
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
renderPassDescriptor.nextInChain = &maxDrawCount;
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.ExecuteBundles(1, &renderBundle);
renderPass.End();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
class MultisampledRenderPassDescriptorValidationTest : public RenderPassDescriptorValidationTest {
public:
utils::ComboRenderPassDescriptor CreateMultisampledRenderPass() {
return utils::ComboRenderPassDescriptor({CreateMultisampledColorTextureView()});
}
wgpu::TextureView CreateMultisampledColorTextureView() {
return CreateColorTextureView(kSampleCount);
}
wgpu::TextureView CreateNonMultisampledColorTextureView() { return CreateColorTextureView(1); }
static constexpr uint32_t kArrayLayers = 1;
static constexpr uint32_t kLevelCount = 1;
static constexpr uint32_t kSize = 32;
static constexpr uint32_t kSampleCount = 4;
static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
private:
wgpu::TextureView CreateColorTextureView(uint32_t sampleCount) {
wgpu::Texture colorTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize,
kArrayLayers, kLevelCount, sampleCount);
return colorTexture.CreateView();
}
};
// Tests on the use of multisampled textures as color attachments
TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledColorAttachments) {
wgpu::TextureView colorTextureView = CreateNonMultisampledColorTextureView();
wgpu::TextureView resolveTargetTextureView = CreateNonMultisampledColorTextureView();
wgpu::TextureView multisampledColorTextureView = CreateMultisampledColorTextureView();
// It is allowed to use a multisampled color attachment without setting resolve target.
{
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
AssertBeginRenderPassSuccess(&renderPass);
}
// It is not allowed to use multiple color attachments with different sample counts.
{
utils::ComboRenderPassDescriptor renderPass(
{multisampledColorTextureView, colorTextureView});
AssertBeginRenderPassError(&renderPass);
}
}
// It is not allowed to use a multisampled resolve target.
TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledResolveTarget) {
wgpu::TextureView multisampledResolveTargetView = CreateMultisampledColorTextureView();
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = multisampledResolveTargetView;
AssertBeginRenderPassError(&renderPass);
}
// It is not allowed to use a resolve target with array layer count > 1.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetArrayLayerMoreThanOne) {
constexpr uint32_t kArrayLayers2 = 2;
wgpu::Texture resolveTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers2, kLevelCount);
wgpu::TextureViewDescriptor viewDesc;
viewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
wgpu::TextureView resolveTextureView = resolveTexture.CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
// It is not allowed to use a resolve target with mipmap level count > 1.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetMipmapLevelMoreThanOne) {
constexpr uint32_t kLevelCount2 = 2;
wgpu::Texture resolveTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers, kLevelCount2);
wgpu::TextureView resolveTextureView = resolveTexture.CreateView();
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
// It is not allowed to use a resolve target which is created from a texture whose usage does
// not include wgpu::TextureUsage::RenderAttachment.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetUsageNoRenderAttachment) {
constexpr wgpu::TextureUsage kUsage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture nonColorUsageResolveTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, 1, kUsage);
wgpu::TextureView nonColorUsageResolveTextureView = nonColorUsageResolveTexture.CreateView();
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = nonColorUsageResolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
// It is not allowed to use a resolve target which is in error state.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetInErrorState) {
wgpu::Texture resolveTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize, kSize, kArrayLayers, kLevelCount);
wgpu::TextureViewDescriptor errorTextureView;
errorTextureView.dimension = wgpu::TextureViewDimension::e2D;
errorTextureView.format = kColorFormat;
errorTextureView.baseArrayLayer = kArrayLayers + 1;
ASSERT_DEVICE_ERROR(wgpu::TextureView errorResolveTarget =
resolveTexture.CreateView(&errorTextureView));
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = errorResolveTarget;
AssertBeginRenderPassError(&renderPass);
}
// It is allowed to use a multisampled color attachment and a non-multisampled resolve target.
TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledColorWithResolveTarget) {
wgpu::TextureView resolveTargetTextureView = CreateNonMultisampledColorTextureView();
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTargetTextureView;
AssertBeginRenderPassSuccess(&renderPass);
}
// It is not allowed to use a resolve target in a format different from the color attachment.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetDifferentFormat) {
constexpr wgpu::TextureFormat kColorFormat2 = wgpu::TextureFormat::BGRA8Unorm;
wgpu::Texture resolveTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat2,
kSize, kSize, kArrayLayers, kLevelCount);
wgpu::TextureView resolveTextureView = resolveTexture.CreateView();
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
// Tests on the size of the resolve target.
TEST_F(MultisampledRenderPassDescriptorValidationTest,
ColorAttachmentResolveTargetDimensionMismatch) {
constexpr uint32_t kSize2 = kSize * 2;
wgpu::Texture resolveTexture = CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat,
kSize2, kSize2, kArrayLayers, kLevelCount + 1);
wgpu::TextureViewDescriptor textureViewDescriptor;
textureViewDescriptor.nextInChain = nullptr;
textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D;
textureViewDescriptor.format = kColorFormat;
textureViewDescriptor.mipLevelCount = 1;
textureViewDescriptor.baseArrayLayer = 0;
textureViewDescriptor.arrayLayerCount = 1;
{
wgpu::TextureViewDescriptor firstMipLevelDescriptor = textureViewDescriptor;
firstMipLevelDescriptor.baseMipLevel = 0;
wgpu::TextureView resolveTextureView = resolveTexture.CreateView(&firstMipLevelDescriptor);
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
{
wgpu::TextureViewDescriptor secondMipLevelDescriptor = textureViewDescriptor;
secondMipLevelDescriptor.baseMipLevel = 1;
wgpu::TextureView resolveTextureView = resolveTexture.CreateView(&secondMipLevelDescriptor);
utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Test the overlaps of multiple resolve target.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetUsedTwice) {
wgpu::TextureView resolveTextureView = CreateNonMultisampledColorTextureView();
wgpu::TextureView colorTextureView1 = CreateMultisampledColorTextureView();
wgpu::TextureView colorTextureView2 = CreateMultisampledColorTextureView();
// It is allowed to use different resolve targets in a render pass.
{
wgpu::TextureView anotherResolveTextureView = CreateNonMultisampledColorTextureView();
utils::ComboRenderPassDescriptor renderPass =
utils::ComboRenderPassDescriptor({colorTextureView1, colorTextureView2});
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
renderPass.cColorAttachments[1].resolveTarget = anotherResolveTextureView;
AssertBeginRenderPassSuccess(&renderPass);
}
// It is not allowed to use a resolve target twice in a render pass.
{
utils::ComboRenderPassDescriptor renderPass =
utils::ComboRenderPassDescriptor({colorTextureView1, colorTextureView2});
renderPass.cColorAttachments[0].resolveTarget = resolveTextureView;
renderPass.cColorAttachments[1].resolveTarget = resolveTextureView;
AssertBeginRenderPassError(&renderPass);
}
}
// Tests the texture format of the resolve target must support being used as resolve target.
TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetFormat) {
for (wgpu::TextureFormat format : utils::kAllTextureFormats) {
if (!utils::TextureFormatSupportsMultisampling(device, format) ||
utils::IsDepthOrStencilFormat(format)) {
continue;
}
wgpu::Texture colorTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, format, kSize, kSize, kArrayLayers,
kLevelCount, kSampleCount);
wgpu::Texture resolveTarget = CreateTexture(device, wgpu::TextureDimension::e2D, format,
kSize, kSize, kArrayLayers, kLevelCount, 1);
utils::ComboRenderPassDescriptor renderPass({colorTexture.CreateView()});
renderPass.cColorAttachments[0].resolveTarget = resolveTarget.CreateView();
if (utils::TextureFormatSupportsResolveTarget(device, format)) {
AssertBeginRenderPassSuccess(&renderPass);
} else {
AssertBeginRenderPassError(&renderPass);
}
}
}
// Tests on the sample count of depth stencil attachment.
TEST_F(MultisampledRenderPassDescriptorValidationTest, DepthStencilAttachmentSampleCount) {
constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
wgpu::Texture multisampledDepthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
kArrayLayers, kLevelCount, kSampleCount);
wgpu::TextureView multisampledDepthStencilTextureView =
multisampledDepthStencilTexture.CreateView();
// It is not allowed to use a depth stencil attachment whose sample count is different from
// the one of the color attachment.
{
wgpu::Texture depthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize,
kArrayLayers, kLevelCount);
wgpu::TextureView depthStencilTextureView = depthStencilTexture.CreateView();
utils::ComboRenderPassDescriptor renderPass({CreateMultisampledColorTextureView()},
depthStencilTextureView);
AssertBeginRenderPassError(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({CreateNonMultisampledColorTextureView()},
multisampledDepthStencilTextureView);
AssertBeginRenderPassError(&renderPass);
}
// It is allowed to use a multisampled depth stencil attachment whose sample count is equal
// to the one of the color attachment.
{
utils::ComboRenderPassDescriptor renderPass({CreateMultisampledColorTextureView()},
multisampledDepthStencilTextureView);
AssertBeginRenderPassSuccess(&renderPass);
}
// It is allowed to use a multisampled depth stencil attachment while there is no color
// attachment.
{
utils::ComboRenderPassDescriptor renderPass({}, multisampledDepthStencilTextureView);
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Creating a render pass with DawnRenderPassColorAttachmentRenderToSingleSampled chained struct
// without MSAARenderToSingleSampled feature enabled should result in error.
TEST_F(MultisampledRenderPassDescriptorValidationTest,
CreateMSAARenderToSingleSampledRenderPassWithoutFeatureEnabled) {
wgpu::TextureView colorTextureView = CreateNonMultisampledColorTextureView();
wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderToSingleSampledDesc;
renderToSingleSampledDesc.implicitSampleCount = 4;
utils::ComboRenderPassDescriptor renderPass({colorTextureView});
renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("feature is not enabled"));
}
// Creating a render pass with LoadOp::ExpandResolveTexture without DawnLoadResolveTexture feature
// enabled should result in error.
TEST_F(MultisampledRenderPassDescriptorValidationTest, LoadResolveTextureWithoutFeatureEnabled) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
auto resolveTarget = CreateNonMultisampledColorTextureView();
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = multisampledColorTextureView;
renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("is not enabled"));
}
// Tests that NaN cannot be accepted as a valid color or depth clear value and INFINITY is valid
// in both color and depth clear values.
TEST_F(RenderPassDescriptorValidationTest, UseNaNOrINFINITYAsColorOrDepthClearValue) {
wgpu::TextureView color = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
// Tests that NaN cannot be used in clearColor.
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.r = NAN;
AssertBeginRenderPassError(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.g = NAN;
AssertBeginRenderPassError(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.b = NAN;
AssertBeginRenderPassError(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.a = NAN;
AssertBeginRenderPassError(&renderPass);
}
// Tests that INFINITY can be used in clearColor.
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.r = INFINITY;
AssertBeginRenderPassSuccess(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.g = INFINITY;
AssertBeginRenderPassSuccess(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.b = INFINITY;
AssertBeginRenderPassSuccess(&renderPass);
}
{
utils::ComboRenderPassDescriptor renderPass({color}, nullptr);
renderPass.cColorAttachments[0].clearValue.a = INFINITY;
AssertBeginRenderPassSuccess(&renderPass);
}
// Tests that NaN cannot be used in depthClearValue.
{
wgpu::TextureView depth =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24Plus);
utils::ComboRenderPassDescriptor renderPass({color}, depth);
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.depthClearValue = NAN;
AssertBeginRenderPassError(&renderPass);
}
// Tests that INFINITY cannot be used in depthClearValue.
{
wgpu::TextureView depth =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24Plus);
utils::ComboRenderPassDescriptor renderPass({color}, depth);
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.depthClearValue = INFINITY;
AssertBeginRenderPassError(&renderPass);
}
// TODO(https://crbug.com/dawn/666): Add a test case for clearStencil for stencilOnly
// once stencil8 is supported.
}
// Tests that depth clear values mut be between 0 and 1, inclusive.
TEST_F(RenderPassDescriptorValidationTest, ValidateDepthClearValueRange) {
wgpu::TextureView depth = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24Plus);
utils::ComboRenderPassDescriptor renderPass({}, depth);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
// 0, 1, and any value in between are be valid.
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 0;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 0.1;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 0.5;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 0.82;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 1;
AssertBeginRenderPassSuccess(&renderPass);
// Values less than 0 or greater than 1 are invalid.
renderPass.cDepthStencilAttachmentInfo.depthClearValue = -1;
AssertBeginRenderPassError(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 2;
AssertBeginRenderPassError(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = -0.001;
AssertBeginRenderPassError(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 1.001;
AssertBeginRenderPassError(&renderPass);
// Clear values are not validated if the depthLoadOp is Load.
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
renderPass.cDepthStencilAttachmentInfo.depthClearValue = -1;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 2;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = -0.001;
AssertBeginRenderPassSuccess(&renderPass);
renderPass.cDepthStencilAttachmentInfo.depthClearValue = 1.001;
AssertBeginRenderPassSuccess(&renderPass);
}
// Tests that default depthClearValue is required if attachment has a depth aspect and depthLoadOp
// is clear.
TEST_F(RenderPassDescriptorValidationTest, DefaultDepthClearValue) {
wgpu::TextureView depthView =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24Plus);
wgpu::TextureView stencilView = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Stencil8);
wgpu::RenderPassDepthStencilAttachment depthStencilAttachment;
wgpu::RenderPassDescriptor renderPassDescriptor;
renderPassDescriptor.colorAttachmentCount = 0;
renderPassDescriptor.colorAttachments = nullptr;
renderPassDescriptor.depthStencilAttachment = &depthStencilAttachment;
// Default depthClearValue should be accepted if attachment doesn't have
// a depth aspect.
depthStencilAttachment.view = stencilView;
depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Load;
depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Store;
AssertBeginRenderPassSuccess(&renderPassDescriptor);
// Default depthClearValue should be accepted if depthLoadOp is not clear.
depthStencilAttachment.view = depthView;
depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Undefined;
depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Undefined;
depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Load;
depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store;
AssertBeginRenderPassSuccess(&renderPassDescriptor);
// Default depthClearValue should fail the validation
// if attachment has a depth aspect and depthLoadOp is clear.
depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Clear;
AssertBeginRenderPassError(&renderPassDescriptor);
// The validation should pass if valid depthClearValue is provided.
depthStencilAttachment.depthClearValue = 0.0f;
AssertBeginRenderPassSuccess(&renderPassDescriptor);
}
// Check the validation rules around depth/stencilReadOnly
TEST_F(RenderPassDescriptorValidationTest, ValidateDepthStencilReadOnly) {
wgpu::TextureView colorView = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::TextureView depthStencilView =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8);
wgpu::TextureView depthStencilViewNoStencil =
Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Depth24Plus);
wgpu::TextureView stencilView = Create2DAttachment(device, 1, 1, wgpu::TextureFormat::Stencil8);
using Aspect = wgpu::TextureAspect;
struct TestSpec {
wgpu::TextureFormat format;
Aspect formatAspects;
Aspect testAspect;
};
TestSpec specs[] = {
{wgpu::TextureFormat::Depth24PlusStencil8, Aspect::All, Aspect::StencilOnly},
{wgpu::TextureFormat::Depth24PlusStencil8, Aspect::All, Aspect::DepthOnly},
{wgpu::TextureFormat::Depth24Plus, Aspect::DepthOnly, Aspect::DepthOnly},
{wgpu::TextureFormat::Stencil8, Aspect::All, Aspect::StencilOnly},
};
for (const auto& spec : specs) {
wgpu::TextureView depthStencil = Create2DAttachment(device, 1, 1, spec.format);
utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
Aspect testAspect = spec.testAspect;
Aspect otherAspect =
testAspect == Aspect::DepthOnly ? Aspect::StencilOnly : Aspect::DepthOnly;
auto Set = [&](Aspect aspect, wgpu::LoadOp loadOp, wgpu::StoreOp storeOp, bool readonly) {
if (aspect == Aspect::DepthOnly) {
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = loadOp;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = storeOp;
renderPass.cDepthStencilAttachmentInfo.depthReadOnly = readonly;
} else {
DAWN_ASSERT(aspect == Aspect::StencilOnly);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = loadOp;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = storeOp;
renderPass.cDepthStencilAttachmentInfo.stencilReadOnly = readonly;
}
};
// Tests that a read-only pass with depth/stencilReadOnly both set to true succeeds.
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassSuccess(&renderPass);
// Tests that readOnly with LoadOp not undefined is invalid.
Set(testAspect, wgpu::LoadOp::Clear, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
Set(testAspect, wgpu::LoadOp::Load, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
// Tests that readOnly with StoreOp not undefined is invalid.
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Store, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Discard, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
// Test for the aspect's not present in the format, if applicable.
if (testAspect != spec.formatAspects) {
// Tests that readOnly with LoadOp not undefined is invalid even if the aspect is not in
// the format.
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Clear, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Load, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassError(&renderPass);
// Tests that readOnly with StoreOp not undefined is invalid even if the aspect is not
// in the format.
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Store, true);
AssertBeginRenderPassError(&renderPass);
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Discard, true);
AssertBeginRenderPassError(&renderPass);
}
// Test that it is allowed to set only one of the aspects readonly.
Set(testAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
Set(otherAspect, wgpu::LoadOp::Load, wgpu::StoreOp::Store, false);
AssertBeginRenderPassSuccess(&renderPass);
Set(testAspect, wgpu::LoadOp::Load, wgpu::StoreOp::Store, false);
Set(otherAspect, wgpu::LoadOp::Undefined, wgpu::StoreOp::Undefined, true);
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Check that the depth stencil attachment must use all aspects.
TEST_F(RenderPassDescriptorValidationTest, ValidateDepthStencilAllAspects) {
wgpu::TextureDescriptor texDesc;
texDesc.usage = wgpu::TextureUsage::RenderAttachment;
texDesc.size = {1, 1, 1};
wgpu::TextureViewDescriptor viewDesc;
viewDesc.baseMipLevel = 0;
viewDesc.mipLevelCount = 1;
viewDesc.baseArrayLayer = 0;
viewDesc.arrayLayerCount = 1;
// Using all aspects of a depth+stencil texture is allowed.
{
texDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
viewDesc.format = wgpu::TextureFormat::Undefined;
viewDesc.aspect = wgpu::TextureAspect::All;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
AssertBeginRenderPassSuccess(&renderPass);
}
// Using only depth of a depth+stencil texture is an error, case without format
// reinterpretation.
{
texDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
viewDesc.format = wgpu::TextureFormat::Undefined;
viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassError(&renderPass);
}
// Using only depth of a depth+stencil texture is an error, case with format reinterpretation.
{
texDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
viewDesc.format = wgpu::TextureFormat::Depth24Plus;
viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassError(&renderPass);
}
// Using only stencil of a depth+stencil texture is an error, case without format
// reinterpration.
{
texDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
viewDesc.format = wgpu::TextureFormat::Undefined;
viewDesc.aspect = wgpu::TextureAspect::StencilOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassError(&renderPass);
}
// Using only stencil of a depth+stencil texture is an error, case with format reinterpretation.
{
texDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
viewDesc.format = wgpu::TextureFormat::Stencil8;
viewDesc.aspect = wgpu::TextureAspect::StencilOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassError(&renderPass);
}
// Using DepthOnly of a depth only texture is allowed.
{
texDesc.format = wgpu::TextureFormat::Depth24Plus;
viewDesc.format = wgpu::TextureFormat::Undefined;
viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassSuccess(&renderPass);
}
// Using StencilOnly of a stencil only texture is allowed.
{
texDesc.format = wgpu::TextureFormat::Stencil8;
viewDesc.format = wgpu::TextureFormat::Undefined;
viewDesc.aspect = wgpu::TextureAspect::StencilOnly;
wgpu::TextureView view = device.CreateTexture(&texDesc).CreateView(&viewDesc);
utils::ComboRenderPassDescriptor renderPass({}, view);
renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
AssertBeginRenderPassSuccess(&renderPass);
}
}
// Tests validation for per-pixel accounting for render targets. The tests currently assume that the
// default maxColorAttachmentBytesPerSample limit of 32 is used.
TEST_F(RenderPassDescriptorValidationTest, RenderPassColorAttachmentBytesPerSample) {
struct TestCase {
std::vector<wgpu::TextureFormat> formats;
bool success;
};
static std::vector<TestCase> kTestCases = {
// Simple 1 format cases.
// R8Unorm take 1 byte and are aligned to 1 byte so we can have 8 (max).
{{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Unorm},
true},
// RGBA8Uint takes 4 bytes and are aligned to 1 byte so we can have 8 (max).
{{wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint,
wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA8Uint},
true},
// RGBA8Unorm takes 8 bytes (special case) and are aligned to 1 byte so only 4 allowed.
{{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm},
true},
{{wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::RGBA8Unorm},
false},
// RGBA32Float takes 16 bytes and are aligned to 4 bytes so only 2 are allowed.
{{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float}, true},
{{wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::RGBA32Float,
wgpu::TextureFormat::RGBA32Float},
false},
// Different format alignment cases.
// Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though 1+4+8+16+1 <
// 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm become 4 and
// 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however is allowed:
// 4+8+16+1+1 < 32.
{{wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R32Float,
wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA32Float,
wgpu::TextureFormat::R8Unorm},
false},
{{wgpu::TextureFormat::R32Float, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::RGBA32Float, wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::R8Unorm},
true},
};
for (const TestCase& testCase : kTestCases) {
std::vector<wgpu::TextureView> colorAttachmentInfo;
for (size_t i = 0; i < testCase.formats.size(); i++) {
colorAttachmentInfo.push_back(Create2DAttachment(device, 1, 1, testCase.formats.at(i)));
}
utils::ComboRenderPassDescriptor descriptor(colorAttachmentInfo);
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&descriptor);
renderPassEncoder.End();
if (testCase.success) {
commandEncoder.Finish();
} else {
ASSERT_DEVICE_ERROR(commandEncoder.Finish());
}
}
}
// TODO(cwallez@chromium.org): Constraints on attachment aliasing?
class MSAARenderToSingleSampledRenderPassDescriptorValidationTest
: public MultisampledRenderPassDescriptorValidationTest {
protected:
void SetUp() override {
MultisampledRenderPassDescriptorValidationTest::SetUp();
mRenderToSingleSampledDesc.implicitSampleCount = kSampleCount;
}
WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
wgpu::DeviceDescriptor descriptor) override {
wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::MSAARenderToSingleSampled};
descriptor.requiredFeatures = requiredFeatures;
descriptor.requiredFeatureCount = 1;
return dawnAdapter.CreateDevice(&descriptor);
}
utils::ComboRenderPassDescriptor CreateMultisampledRenderToSingleSampledRenderPass(
wgpu::TextureView colorAttachment,
wgpu::TextureView depthStencilAttachment = nullptr) {
utils::ComboRenderPassDescriptor renderPass({colorAttachment}, depthStencilAttachment);
renderPass.cColorAttachments[0].nextInChain = &mRenderToSingleSampledDesc;
return renderPass;
}
// Create a view for a texture that can be used with a MSAA render to single sampled render
// pass.
wgpu::TextureView CreateCompatibleColorTextureView() {
wgpu::Texture colorTexture = CreateTexture(
device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, /*sampleCount=*/1,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
return colorTexture.CreateView();
}
private:
wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled mRenderToSingleSampledDesc;
};
// Test that using a valid color attachment with enabled MSAARenderToSingleSampled doesn't raise any
// error.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ColorAttachmentValid) {
// Create a texture with sample count = 1.
auto textureView = CreateCompatibleColorTextureView();
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(textureView);
AssertBeginRenderPassSuccess(&renderPass);
}
// When MSAARenderToSingleSampled is enabled for a color attachment, it must be created with
// TextureBinding usage.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ColorAttachmentInvalidUsage) {
// Create a texture with sample count = 1.
auto texture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(texture.CreateView());
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("usage"));
}
// When MSAARenderToSingleSampled is enabled for a color attachment, there must be no explicit
// resolve target specified for it.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, ErrorSettingResolveTarget) {
// Create a texture with sample count = 1.
auto textureView1 = CreateCompatibleColorTextureView();
auto textureView2 = CreateCompatibleColorTextureView();
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(textureView1);
renderPass.cColorAttachments[0].resolveTarget = textureView2;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("as a resolve target"));
}
// Using unsupported implicit sample count in DawnRenderPassColorAttachmentRenderToSingleSampled
// chained struct should result in error.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, UnsupportedSampleCountError) {
// Create a texture with sample count = 1.
auto textureView = CreateCompatibleColorTextureView();
// Create a render pass with implicit sample count = 3. Which is not supported.
wgpu::DawnRenderPassColorAttachmentRenderToSingleSampled renderToSingleSampledDesc;
renderToSingleSampledDesc.implicitSampleCount = 3;
utils::ComboRenderPassDescriptor renderPass({textureView});
renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
AssertBeginRenderPassError(&renderPass,
testing::HasSubstr("implicit sample count (3) is not supported"));
// Create a render pass with implicit sample count = 1. Which is also not supported.
renderToSingleSampledDesc.implicitSampleCount = 1;
renderPass.cColorAttachments[0].nextInChain = &renderToSingleSampledDesc;
AssertBeginRenderPassError(&renderPass,
testing::HasSubstr("implicit sample count (1) is not supported"));
}
// When MSAARenderToSingleSampled is enabled in a color attachment, there should be an error if a
// color attachment's format doesn't support resolve. Example, RGBA8Sint format.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, UnresolvableColorFormatError) {
// Create a texture with sample count = 1.
auto texture =
CreateTexture(device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::RGBA8Sint, kSize,
kSize, kArrayLayers, kLevelCount, /*sampleCount=*/1,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(texture.CreateView());
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not support resolve"));
}
// Depth stencil attachment's sample count must match the one specified in color attachment's
// implicitSampleCount.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest, DepthStencilSampleCountValid) {
// Create a color texture with sample count = 1.
auto colorTextureView = CreateCompatibleColorTextureView();
// Create depth stencil texture with sample count = 4.
auto depthStencilTexture = CreateTexture(
device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::Depth24PlusStencil8, kSize, kSize,
1, 1, /*sampleCount=*/kSampleCount, wgpu::TextureUsage::RenderAttachment);
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(
colorTextureView, depthStencilTexture.CreateView());
AssertBeginRenderPassSuccess(&renderPass);
}
// Using depth stencil attachment with sample count not matching the implicit sample count will
// result in error.
TEST_F(MSAARenderToSingleSampledRenderPassDescriptorValidationTest,
DepthStencilSampleCountNotMatchImplicitSampleCount) {
// Create a color texture with sample count = 1.
auto colorTextureView = CreateCompatibleColorTextureView();
// Create depth stencil texture with sample count = 1. Which doesn't match implicitSampleCount=4
// specified in mRenderToSingleSampledDesc.
auto depthStencilTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::Depth24PlusStencil8,
kSize, kSize, 1, 1, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
auto renderPass = CreateMultisampledRenderToSingleSampledRenderPass(
colorTextureView, depthStencilTexture.CreateView());
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not match the sample count"));
}
class DawnLoadResolveTextureValidationTest : public MultisampledRenderPassDescriptorValidationTest {
protected:
WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
wgpu::DeviceDescriptor descriptor) override {
wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::DawnLoadResolveTexture};
descriptor.requiredFeatures = requiredFeatures;
descriptor.requiredFeatureCount = 1;
return dawnAdapter.CreateDevice(&descriptor);
}
// Create a view for a resolve texture that can be used with LoadOp::ExpandResolveTexture.
wgpu::TextureView CreateCompatibleResolveTextureView() {
wgpu::Texture colorTexture = CreateTexture(
device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, /*sampleCount=*/1,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
return colorTexture.CreateView();
}
};
// Test that using a valid resolve texture with LoadOp::ExpandResolveTexture doesn't raise
// any error.
TEST_F(DawnLoadResolveTextureValidationTest, ResolveTargetValid) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
// Create a resolve texture with sample count = 1.
auto resolveTarget = CreateCompatibleResolveTextureView();
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = multisampledColorTextureView;
renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassSuccess(&renderPass);
}
// When LoadOp::ExpandResolveTexture is used, a resolve texture view must be set.
TEST_F(DawnLoadResolveTextureValidationTest, ResolveTargetMustBeSet) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
// Error case: texture view is set but resolveTarget is not set.
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = multisampledColorTextureView;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("resolve target"));
}
// When LoadOp::ExpandResolveTexture is used, the attached texture view must be multisampled.
TEST_F(DawnLoadResolveTextureValidationTest, ResolveTargetInvalidSampleCount) {
// Create a texture with sample count = 1.
auto colorTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
// Create a resolve texture with sample count = 1.
auto resolveTarget = CreateCompatibleResolveTextureView();
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = colorTexture.CreateView();
renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("sample count"));
}
// When LoadOp::ExpandResolveTexture is used, the resolve texture must be created with
// TextureBinding usage.
TEST_F(DawnLoadResolveTextureValidationTest, ResolveTargetInvalidUsage) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
// Create a texture with sample count = 1.
auto resolveTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
kLevelCount, /*sampleCount=*/1, wgpu::TextureUsage::RenderAttachment);
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = multisampledColorTextureView;
renderPass.cColorAttachments[0].resolveTarget = resolveTexture.CreateView();
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("usage"));
}
// When LoadOp::ExpandResolveTexture is enabled in a color attachment, there should be an error if a
// color attachment's format doesn't support resolve. Example, RGBA8Sint format.
TEST_F(DawnLoadResolveTextureValidationTest, UnresolvableColorFormatError) {
auto multisampledTexture = CreateTexture(
device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::RGBA8Sint, kSize, kSize,
kArrayLayers, kLevelCount, /*sampleCount=*/4, wgpu::TextureUsage::RenderAttachment);
// Create a texture with sample count = 1.
auto resolveTexture =
CreateTexture(device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::RGBA8Sint, kSize,
kSize, kArrayLayers, kLevelCount, /*sampleCount=*/1,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
auto renderPass = CreateMultisampledRenderPass();
renderPass.cColorAttachments[0].view = multisampledTexture.CreateView();
renderPass.cColorAttachments[0].resolveTarget = resolveTexture.CreateView();
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("does not support resolve"));
}
// LoadOp::ExpandResolveTexture can only be used in a render pass with single color attachment.
// The LoadOp is NOT currently supported on depth/stencil attachment either.
TEST_F(DawnLoadResolveTextureValidationTest, OnlyLoadingSingleColorAttachmentIsSupported) {
auto multisampledColorTextureView = CreateMultisampledColorTextureView();
auto resolveTarget = CreateCompatibleResolveTextureView();
// Error case: Use ExpandResolveTexture with multiple color attachments.
{
auto multisampledColorTextureView2 = CreateMultisampledColorTextureView();
auto resolveTarget2 = CreateCompatibleResolveTextureView();
auto renderPass = CreateMultisampledRenderPass();
renderPass.colorAttachmentCount = 2;
renderPass.cColorAttachments[0].view = multisampledColorTextureView;
renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
renderPass.cColorAttachments[1].view = multisampledColorTextureView2;
renderPass.cColorAttachments[1].resolveTarget = resolveTarget2;
renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass, testing::HasSubstr("colorAttachmentCount"));
}
// Error case: Use ExpandResolveTexture on depth/stencil attachment.
{
// Create depth stencil texture with sample count = 4.
auto depthStencilTexture = CreateTexture(
device, wgpu::TextureDimension::e2D, wgpu::TextureFormat::Depth24PlusStencil8, kSize,
kSize, 1, 1, /*sampleCount=*/kSampleCount, wgpu::TextureUsage::RenderAttachment);
auto renderPass = utils::ComboRenderPassDescriptor({multisampledColorTextureView},
depthStencilTexture.CreateView());
renderPass.cColorAttachments[0].resolveTarget = resolveTarget;
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::ExpandResolveTexture;
renderPass.cDepthStencilAttachmentInfo.depthLoadOp =
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp =
wgpu::LoadOp::ExpandResolveTexture;
AssertBeginRenderPassError(&renderPass,
testing::HasSubstr("not supported on depth/stencil attachment"));
}
}
} // anonymous namespace
} // namespace dawn