blob: 1d00efb90189114e9871293bdd3d03b03d8897b7 [file] [log] [blame]
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <vector>
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/TextureUtils.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
constexpr static uint32_t kSize = 4;
using TextureFormat = wgpu::TextureFormat;
DAWN_TEST_PARAM_STRUCT(ReadOnlyDepthStencilAttachmentTestsParams, TextureFormat);
class ReadOnlyDepthStencilAttachmentTests
: public DawnTestWithParams<ReadOnlyDepthStencilAttachmentTestsParams> {
protected:
struct DepthStencilValues {
float depthInitValue;
uint32_t stencilInitValue;
uint32_t stencilRefValue;
};
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
switch (GetParam().mTextureFormat) {
case wgpu::TextureFormat::Depth32FloatStencil8:
if (SupportsFeatures({wgpu::FeatureName::Depth32FloatStencil8})) {
mIsFormatSupported = true;
return {wgpu::FeatureName::Depth32FloatStencil8};
}
return {};
default:
mIsFormatSupported = true;
return {};
}
}
bool IsFormatSupported() const { return mIsFormatSupported; }
wgpu::RenderPipeline CreateRenderPipeline(wgpu::TextureAspect aspect,
wgpu::TextureFormat format,
bool sampleFromAttachment) {
utils::ComboRenderPipelineDescriptor pipelineDescriptor;
// Draw a rectangle via two triangles. The depth value of the top of the rectangle is 0.4.
// The depth value of the bottom is 0.0. The depth value gradually change from 0.4 to 0.0
// from the top to the bottom. The top part will compare with the depth values and fail to
// pass the depth test. The bottom part will compare with the depth values in depth buffer
// and pass the depth test, and sample from the depth buffer in fragment shader in the same
// pipeline.
pipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
var pos = array(
vec3f(-1.0, 1.0, 0.4),
vec3f(-1.0, -1.0, 0.0),
vec3f( 1.0, 1.0, 0.4),
vec3f( 1.0, 1.0, 0.4),
vec3f(-1.0, -1.0, 0.0),
vec3f( 1.0, -1.0, 0.0));
return vec4f(pos[VertexIndex], 1.0);
})");
if (!sampleFromAttachment) {
// Draw a solid blue into color buffer if not sample from depth/stencil attachment.
pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4f {
return vec4f(0.0, 0.0, 1.0, 0.0);
})");
} else {
// Sample from depth/stencil attachment and draw that sampled texel into color buffer.
if (aspect == wgpu::TextureAspect::DepthOnly) {
pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var samp : sampler;
@group(0) @binding(1) var tex : texture_depth_2d;
@fragment
fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f {
return vec4f(textureSample(tex, samp, FragCoord.xy), 0.0, 0.0, 0.0);
})");
} else {
DAWN_ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var tex : texture_2d<u32>;
@fragment
fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f {
var texel = textureLoad(tex, vec2i(FragCoord.xy), 0);
return vec4f(f32(texel[0]) / 255.0, 0.0, 0.0, 0.0);
})");
}
}
// Enable depth or stencil test. But depth/stencil write is not enabled.
wgpu::DepthStencilState* depthStencil = pipelineDescriptor.EnableDepthStencil(format);
if (aspect == wgpu::TextureAspect::DepthOnly) {
depthStencil->depthCompare = wgpu::CompareFunction::LessEqual;
} else {
depthStencil->stencilFront.compare = wgpu::CompareFunction::LessEqual;
}
return device.CreateRenderPipeline(&pipelineDescriptor);
}
wgpu::Texture CreateTexture(wgpu::TextureFormat format, wgpu::TextureUsage usage) {
wgpu::TextureDescriptor descriptor = {};
descriptor.size = {kSize, kSize, 1};
descriptor.format = format;
descriptor.usage = usage;
return device.CreateTexture(&descriptor);
}
void DoTest(wgpu::TextureAspect aspect,
wgpu::TextureFormat format,
wgpu::Texture colorTexture,
DepthStencilValues* values,
bool sampleFromAttachment) {
wgpu::TextureUsage dsTextureUsage = wgpu::TextureUsage::RenderAttachment;
if (sampleFromAttachment) {
dsTextureUsage |= wgpu::TextureUsage::TextureBinding;
}
wgpu::Texture depthStencilTexture = CreateTexture(format, dsTextureUsage);
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
// Note that we must encompass all aspects for texture view used in attachment.
wgpu::TextureView depthStencilViewInAttachment = depthStencilTexture.CreateView();
utils::ComboRenderPassDescriptor passDescriptorInit({}, depthStencilViewInAttachment);
passDescriptorInit.UnsetDepthStencilLoadStoreOpsForFormat(format);
if (aspect == wgpu::TextureAspect::DepthOnly) {
passDescriptorInit.cDepthStencilAttachmentInfo.depthClearValue = values->depthInitValue;
} else {
DAWN_ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
passDescriptorInit.cDepthStencilAttachmentInfo.stencilClearValue =
values->stencilInitValue;
}
wgpu::RenderPassEncoder passInit = commandEncoder.BeginRenderPass(&passDescriptorInit);
passInit.End();
// Note that we can only select one single aspect for texture view used in bind group.
wgpu::TextureViewDescriptor viewDesc = {};
viewDesc.aspect = aspect;
wgpu::TextureView depthStencilViewInBindGroup = depthStencilTexture.CreateView(&viewDesc);
// Create a render pass to initialize the depth/stencil attachment.
utils::ComboRenderPassDescriptor passDescriptor({colorTexture.CreateView()},
depthStencilViewInAttachment);
// Set both aspects to readonly. We have to do this if the format has both aspects, or
// it doesn't impact anything if the format has only one aspect.
passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
// Create a render pass with readonly depth/stencil attachment. The attachment has already
// been initialized. The pipeline in this render pass will sample from the attachment.
// The pipeline will read from the attachment to do depth/stencil test too.
wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor);
wgpu::RenderPipeline pipeline = CreateRenderPipeline(aspect, format, sampleFromAttachment);
pass.SetPipeline(pipeline);
if (aspect == wgpu::TextureAspect::DepthOnly) {
if (sampleFromAttachment) {
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, device.CreateSampler()}, {1, depthStencilViewInBindGroup}});
pass.SetBindGroup(0, bindGroup);
}
} else {
DAWN_ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
if (sampleFromAttachment) {
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, depthStencilViewInBindGroup}});
pass.SetBindGroup(0, bindGroup);
}
pass.SetStencilReference(values->stencilRefValue);
}
pass.Draw(6);
pass.End();
wgpu::CommandBuffer commands = commandEncoder.Finish();
queue.Submit(1, &commands);
}
private:
bool mIsFormatSupported = false;
};
class ReadOnlyDepthAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {
protected:
void SetUp() override {
ReadOnlyDepthStencilAttachmentTests::SetUp();
DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported());
}
};
TEST_P(ReadOnlyDepthAttachmentTests, SampleFromAttachment) {
wgpu::Texture colorTexture =
CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
wgpu::TextureFormat depthFormat = GetParam().mTextureFormat;
DepthStencilValues values;
values.depthInitValue = 0.2;
DoTest(wgpu::TextureAspect::DepthOnly, depthFormat, colorTexture, &values, true);
// The top part is not rendered by the pipeline. Its color is the default clear color for
// color attachment.
const std::vector<utils::RGBA8> kExpectedTopColors(kSize * kSize / 2, {0, 0, 0, 0});
// The bottom part is rendered, whose red channel is sampled from depth attachment, which
// is initialized into 0.2.
const std::vector<utils::RGBA8> kExpectedBottomColors(
kSize * kSize / 2, {static_cast<uint8_t>(0.2 * 255), 0, 0, 0});
EXPECT_TEXTURE_EQ(kExpectedTopColors.data(), colorTexture, {0, 0}, {kSize, kSize / 2});
EXPECT_TEXTURE_EQ(kExpectedBottomColors.data(), colorTexture, {0, kSize / 2},
{kSize, kSize / 2});
}
TEST_P(ReadOnlyDepthAttachmentTests, NotSampleFromAttachment) {
wgpu::Texture colorTexture =
CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
wgpu::TextureFormat depthFormat = GetParam().mTextureFormat;
DepthStencilValues values;
values.depthInitValue = 0.2;
DoTest(wgpu::TextureAspect::DepthOnly, depthFormat, colorTexture, &values, false);
// The top part is not rendered by the pipeline. Its color is the default clear color for
// color attachment.
const std::vector<utils::RGBA8> kExpectedTopColors(kSize * kSize / 2, {0, 0, 0, 0});
// The bottom part is rendered. Its color is set to blue.
const std::vector<utils::RGBA8> kExpectedBottomColors(kSize * kSize / 2, {0, 0, 255, 0});
EXPECT_TEXTURE_EQ(kExpectedTopColors.data(), colorTexture, {0, 0}, {kSize, kSize / 2});
EXPECT_TEXTURE_EQ(kExpectedBottomColors.data(), colorTexture, {0, kSize / 2},
{kSize, kSize / 2});
}
// Regression test for crbug.com/dawn/1512 where having aspectReadOnly for an unused aspect of a
// depth-stencil texture would cause the attachment to be considered read-only, causing layout
// mismatch issues.
TEST_P(ReadOnlyDepthAttachmentTests, UnusedAspectWithReadOnly) {
wgpu::TextureFormat format = GetParam().mTextureFormat;
wgpu::Texture depthStencilTexture = CreateTexture(format, wgpu::TextureUsage::RenderAttachment);
utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView());
if (utils::IsStencilOnlyFormat(format)) {
passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Undefined;
passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Undefined;
} else {
passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = false;
}
if (utils::IsDepthOnlyFormat(format)) {
passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Undefined;
passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Undefined;
} else {
passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = false;
}
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
}
class ReadOnlyStencilAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {
protected:
void SetUp() override {
ReadOnlyDepthStencilAttachmentTests::SetUp();
DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported());
}
};
TEST_P(ReadOnlyStencilAttachmentTests, SampleFromAttachment) {
wgpu::Texture colorTexture =
CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
wgpu::TextureFormat stencilFormat = GetParam().mTextureFormat;
DepthStencilValues values;
values.stencilInitValue = 3;
values.stencilRefValue = 2;
// stencilRefValue < stencilValue (stencilInitValue), so stencil test passes. The pipeline
// samples from stencil buffer and writes into color buffer.
DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values, true);
const std::vector<utils::RGBA8> kSampledColors(kSize * kSize, {3, 0, 0, 0});
EXPECT_TEXTURE_EQ(kSampledColors.data(), colorTexture, {0, 0}, {kSize, kSize});
values.stencilInitValue = 1;
// stencilRefValue > stencilValue (stencilInitValue), so stencil test fails. The pipeline
// doesn't change color buffer. Sampled data from stencil buffer is discarded.
DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values, true);
const std::vector<utils::RGBA8> kInitColors(kSize * kSize, {0, 0, 0, 0});
EXPECT_TEXTURE_EQ(kInitColors.data(), colorTexture, {0, 0}, {kSize, kSize});
}
TEST_P(ReadOnlyStencilAttachmentTests, NotSampleFromAttachment) {
wgpu::Texture colorTexture =
CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
wgpu::TextureFormat stencilFormat = GetParam().mTextureFormat;
DepthStencilValues values;
values.stencilInitValue = 3;
values.stencilRefValue = 2;
// stencilRefValue < stencilValue (stencilInitValue), so stencil test passes. The pipeline
// draw solid blue into color buffer.
DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values, false);
const std::vector<utils::RGBA8> kSampledColors(kSize * kSize, {0, 0, 255, 0});
EXPECT_TEXTURE_EQ(kSampledColors.data(), colorTexture, {0, 0}, {kSize, kSize});
values.stencilInitValue = 1;
// stencilRefValue > stencilValue (stencilInitValue), so stencil test fails. The pipeline
// doesn't change color buffer. drawing data is discarded.
DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values, false);
const std::vector<utils::RGBA8> kInitColors(kSize * kSize, {0, 0, 0, 0});
EXPECT_TEXTURE_EQ(kInitColors.data(), colorTexture, {0, 0}, {kSize, kSize});
}
DAWN_INSTANTIATE_TEST_P(ReadOnlyDepthAttachmentTests,
{D3D11Backend(), D3D12Backend(),
D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(),
VulkanBackend()},
std::vector<wgpu::TextureFormat>(utils::kDepthFormats.begin(),
utils::kDepthFormats.end()));
DAWN_INSTANTIATE_TEST_P(ReadOnlyStencilAttachmentTests,
{D3D11Backend(), D3D12Backend(),
D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(),
VulkanBackend()},
std::vector<wgpu::TextureFormat>(utils::kStencilFormats.begin(),
utils::kStencilFormats.end()));
} // anonymous namespace
} // namespace dawn