Add support for multisampled sampling
This CL adds support for multisampled sampling on the D3D12 backend. This was already working on other backends. It also adds tests that all of the sample locations are correct. Bug: dawn:431 Change-Id: I6849e5e2d708ad4824e6db2665d668d43a4ef5ea Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23245 Commit-Queue: Austin Eng <enga@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
parent
4e4ebe87ad
commit
8f9523eb38
|
@ -96,15 +96,16 @@ config("dawn_internal") {
|
|||
"-Wc++11-narrowing",
|
||||
"-Wdeprecated-copy",
|
||||
"-Wextra-semi-stmt",
|
||||
"-Wimplicit-fallthrough",
|
||||
"-Winconsistent-missing-destructor-override",
|
||||
"-Winvalid-offsetof",
|
||||
"-Wmissing-field-initializers",
|
||||
"-Wnon-c-typedef-for-linkage",
|
||||
"-Wpessimizing-move",
|
||||
"-Wreturn-std-move-in-c++11",
|
||||
"-Wshadow-field",
|
||||
"-Wstrict-prototypes",
|
||||
"-Wtautological-unsigned-zero-compare",
|
||||
"-Wnon-c-typedef-for-linkage",
|
||||
]
|
||||
|
||||
if (is_win) {
|
||||
|
|
|
@ -107,4 +107,10 @@ extern void __cdecl __debugbreak(void);
|
|||
# define DAWN_FORCE_INLINE inline
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
# define DAWN_FALLTHROUGH [[clang::fallthrough]]
|
||||
#else
|
||||
# define DAWN_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#endif // COMMON_COMPILER_H_
|
||||
|
|
|
@ -129,15 +129,60 @@ namespace dawn_native {
|
|||
case wgpu::TextureViewDimension::e2D:
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
case wgpu::TextureViewDimension::e3D:
|
||||
case wgpu::TextureViewDimension::Undefined:
|
||||
return {};
|
||||
|
||||
case wgpu::TextureViewDimension::Undefined:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
MaybeError ValidateBindingCanBeMultisampled(wgpu::BindingType bindingType,
|
||||
wgpu::TextureViewDimension viewDimension) {
|
||||
switch (bindingType) {
|
||||
case wgpu::BindingType::ReadonlyStorageTexture:
|
||||
case wgpu::BindingType::WriteonlyStorageTexture:
|
||||
return DAWN_VALIDATION_ERROR("Storage textures may not be multisampled");
|
||||
|
||||
case wgpu::BindingType::SampledTexture:
|
||||
break;
|
||||
|
||||
case wgpu::BindingType::StorageBuffer:
|
||||
case wgpu::BindingType::UniformBuffer:
|
||||
case wgpu::BindingType::ReadonlyStorageBuffer:
|
||||
case wgpu::BindingType::Sampler:
|
||||
case wgpu::BindingType::ComparisonSampler:
|
||||
case wgpu::BindingType::StorageTexture:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (viewDimension) {
|
||||
case wgpu::TextureViewDimension::e2D:
|
||||
break;
|
||||
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
return DAWN_VALIDATION_ERROR("2D array textures may not be multisampled");
|
||||
|
||||
case wgpu::TextureViewDimension::Cube:
|
||||
case wgpu::TextureViewDimension::CubeArray:
|
||||
return DAWN_VALIDATION_ERROR("Cube textures may not be multisampled");
|
||||
|
||||
case wgpu::TextureViewDimension::e3D:
|
||||
return DAWN_VALIDATION_ERROR("3D textures may not be multisampled");
|
||||
|
||||
case wgpu::TextureViewDimension::e1D:
|
||||
case wgpu::TextureViewDimension::Undefined:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
|
||||
const BindGroupLayoutDescriptor* descriptor) {
|
||||
if (descriptor->nextInChain != nullptr) {
|
||||
|
@ -155,8 +200,10 @@ namespace dawn_native {
|
|||
DAWN_TRY(ValidateBindingType(entry.type));
|
||||
DAWN_TRY(ValidateTextureComponentType(entry.textureComponentType));
|
||||
|
||||
wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D;
|
||||
if (entry.viewDimension != wgpu::TextureViewDimension::Undefined) {
|
||||
DAWN_TRY(ValidateTextureViewDimension(entry.viewDimension));
|
||||
viewDimension = entry.viewDimension;
|
||||
}
|
||||
|
||||
if (bindingsSet.count(bindingNumber) != 0) {
|
||||
|
@ -167,7 +214,11 @@ namespace dawn_native {
|
|||
|
||||
DAWN_TRY(ValidateStorageTextureFormat(device, entry.type, entry.storageTextureFormat));
|
||||
|
||||
DAWN_TRY(ValidateStorageTextureViewDimension(entry.type, entry.viewDimension));
|
||||
DAWN_TRY(ValidateStorageTextureViewDimension(entry.type, viewDimension));
|
||||
|
||||
if (entry.multisampled) {
|
||||
DAWN_TRY(ValidateBindingCanBeMultisampled(entry.type, viewDimension));
|
||||
}
|
||||
|
||||
switch (entry.type) {
|
||||
case wgpu::BindingType::UniformBuffer:
|
||||
|
@ -194,11 +245,6 @@ namespace dawn_native {
|
|||
return DAWN_VALIDATION_ERROR("storage textures aren't supported (yet)");
|
||||
}
|
||||
|
||||
if (entry.multisampled) {
|
||||
return DAWN_VALIDATION_ERROR(
|
||||
"BindGroupLayoutEntry::multisampled must be false (for now)");
|
||||
}
|
||||
|
||||
bindingsSet.insert(bindingNumber);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,9 @@ namespace dawn_native {
|
|||
MaybeError ValidateStorageTextureViewDimension(wgpu::BindingType bindingType,
|
||||
wgpu::TextureViewDimension dimension);
|
||||
|
||||
MaybeError ValidateBindingCanBeMultisampled(wgpu::BindingType bindingType,
|
||||
wgpu::TextureViewDimension viewDimension);
|
||||
|
||||
// Bindings are specified as a |BindingNumber| in the BindGroupLayoutDescriptor.
|
||||
// These numbers may be arbitrary and sparse. Internally, Dawn packs these numbers
|
||||
// into a packed range of |BindingIndex| integers.
|
||||
|
|
|
@ -145,10 +145,6 @@ namespace dawn_native {
|
|||
BindingNumber bindingNumber = it.first;
|
||||
const ShaderModuleBase::ShaderBindingInfo& bindingInfo = it.second;
|
||||
|
||||
if (bindingInfo.multisampled) {
|
||||
return DAWN_VALIDATION_ERROR("Multisampled textures not supported (yet)");
|
||||
}
|
||||
|
||||
BindGroupLayoutEntry bindingSlot;
|
||||
bindingSlot.binding = static_cast<uint32_t>(bindingNumber);
|
||||
|
||||
|
@ -159,6 +155,11 @@ namespace dawn_native {
|
|||
DAWN_TRY(ValidateStorageTextureViewDimension(bindingInfo.type,
|
||||
bindingInfo.viewDimension));
|
||||
|
||||
if (bindingInfo.multisampled) {
|
||||
DAWN_TRY(ValidateBindingCanBeMultisampled(bindingInfo.type,
|
||||
bindingInfo.viewDimension));
|
||||
}
|
||||
|
||||
bindingSlot.visibility =
|
||||
GetShaderStageVisibilityWithBindingType(bindingInfo.type);
|
||||
|
||||
|
|
|
@ -992,35 +992,50 @@ namespace dawn_native { namespace d3d12 {
|
|||
// Currently we always use D3D12_TEX2D_ARRAY_SRV because we cannot specify base array layer
|
||||
// and layer count in D3D12_TEX2D_SRV. For 2D texture views, we treat them as 1-layer 2D
|
||||
// array textures.
|
||||
// Multisampled textures may only be one array layer, so we use
|
||||
// D3D12_SRV_DIMENSION_TEXTURE2DMS.
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_srv
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array_srv
|
||||
// TODO(jiawei.shao@intel.com): support more texture view dimensions.
|
||||
// TODO(jiawei.shao@intel.com): support creating SRV on multisampled textures.
|
||||
switch (descriptor->dimension) {
|
||||
case wgpu::TextureViewDimension::e2D:
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
|
||||
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||
mSrvDesc.Texture2DArray.ArraySize = descriptor->arrayLayerCount;
|
||||
mSrvDesc.Texture2DArray.FirstArraySlice = descriptor->baseArrayLayer;
|
||||
mSrvDesc.Texture2DArray.MipLevels = descriptor->mipLevelCount;
|
||||
mSrvDesc.Texture2DArray.MostDetailedMip = descriptor->baseMipLevel;
|
||||
mSrvDesc.Texture2DArray.PlaneSlice = 0;
|
||||
mSrvDesc.Texture2DArray.ResourceMinLODClamp = 0;
|
||||
break;
|
||||
case wgpu::TextureViewDimension::Cube:
|
||||
case wgpu::TextureViewDimension::CubeArray:
|
||||
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
|
||||
ASSERT(descriptor->arrayLayerCount % 6 == 0);
|
||||
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
|
||||
mSrvDesc.TextureCubeArray.First2DArrayFace = descriptor->baseArrayLayer;
|
||||
mSrvDesc.TextureCubeArray.NumCubes = descriptor->arrayLayerCount / 6;
|
||||
mSrvDesc.TextureCubeArray.MostDetailedMip = descriptor->baseMipLevel;
|
||||
mSrvDesc.TextureCubeArray.MipLevels = descriptor->mipLevelCount;
|
||||
mSrvDesc.TextureCubeArray.ResourceMinLODClamp = 0;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
if (GetTexture()->IsMultisampledTexture()) {
|
||||
switch (descriptor->dimension) {
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
ASSERT(texture->GetArrayLayers() == 1);
|
||||
DAWN_FALLTHROUGH;
|
||||
case wgpu::TextureViewDimension::e2D:
|
||||
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
|
||||
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
switch (descriptor->dimension) {
|
||||
case wgpu::TextureViewDimension::e2D:
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
|
||||
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||
mSrvDesc.Texture2DArray.ArraySize = descriptor->arrayLayerCount;
|
||||
mSrvDesc.Texture2DArray.FirstArraySlice = descriptor->baseArrayLayer;
|
||||
mSrvDesc.Texture2DArray.MipLevels = descriptor->mipLevelCount;
|
||||
mSrvDesc.Texture2DArray.MostDetailedMip = descriptor->baseMipLevel;
|
||||
mSrvDesc.Texture2DArray.PlaneSlice = 0;
|
||||
mSrvDesc.Texture2DArray.ResourceMinLODClamp = 0;
|
||||
break;
|
||||
case wgpu::TextureViewDimension::Cube:
|
||||
case wgpu::TextureViewDimension::CubeArray:
|
||||
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
|
||||
ASSERT(descriptor->arrayLayerCount % 6 == 0);
|
||||
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
|
||||
mSrvDesc.TextureCubeArray.First2DArrayFace = descriptor->baseArrayLayer;
|
||||
mSrvDesc.TextureCubeArray.NumCubes = descriptor->arrayLayerCount / 6;
|
||||
mSrvDesc.TextureCubeArray.MostDetailedMip = descriptor->baseMipLevel;
|
||||
mSrvDesc.TextureCubeArray.MipLevels = descriptor->mipLevelCount;
|
||||
mSrvDesc.TextureCubeArray.ResourceMinLODClamp = 0;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ namespace dawn_native { namespace opengl {
|
|||
}
|
||||
|
||||
const std::vector<GLuint>& PipelineGL::GetTextureUnitsForTextureView(GLuint index) const {
|
||||
ASSERT(index < mUnitsForSamplers.size());
|
||||
ASSERT(index < mUnitsForTextures.size());
|
||||
return mUnitsForTextures[index];
|
||||
}
|
||||
|
||||
|
|
|
@ -46,11 +46,15 @@ namespace dawn_native { namespace opengl {
|
|||
}
|
||||
|
||||
GLenum TargetForTextureViewDimension(wgpu::TextureViewDimension dimension,
|
||||
uint32_t arrayLayerCount,
|
||||
uint32_t sampleCount) {
|
||||
switch (dimension) {
|
||||
case wgpu::TextureViewDimension::e2D:
|
||||
return (sampleCount > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
case wgpu::TextureViewDimension::e2DArray:
|
||||
if (arrayLayerCount == 1) {
|
||||
return (sampleCount > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
}
|
||||
ASSERT(sampleCount == 1);
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
case wgpu::TextureViewDimension::Cube:
|
||||
|
@ -387,7 +391,8 @@ namespace dawn_native { namespace opengl {
|
|||
|
||||
TextureView::TextureView(TextureBase* texture, const TextureViewDescriptor* descriptor)
|
||||
: TextureViewBase(texture, descriptor), mOwnsHandle(false) {
|
||||
mTarget = TargetForTextureViewDimension(descriptor->dimension, texture->GetSampleCount());
|
||||
mTarget = TargetForTextureViewDimension(descriptor->dimension, descriptor->arrayLayerCount,
|
||||
texture->GetSampleCount());
|
||||
|
||||
if (!UsageNeedsTextureView(texture->GetUsage())) {
|
||||
mHandle = 0;
|
||||
|
|
|
@ -282,6 +282,7 @@ source_set("dawn_end2end_tests_sources") {
|
|||
"end2end/GpuMemorySynchronizationTests.cpp",
|
||||
"end2end/IndexFormatTests.cpp",
|
||||
"end2end/MultisampledRenderingTests.cpp",
|
||||
"end2end/MultisampledSamplingTests.cpp",
|
||||
"end2end/NonzeroBufferCreationTests.cpp",
|
||||
"end2end/NonzeroTextureCreationTests.cpp",
|
||||
"end2end/ObjectCachingTests.cpp",
|
||||
|
|
|
@ -46,6 +46,14 @@
|
|||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \
|
||||
new ::detail::ExpectEq<uint32_t>(expected, count))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t), \
|
||||
new ::detail::ExpectEq<float>(expected))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \
|
||||
new ::detail::ExpectEq<float>(expected, count))
|
||||
|
||||
// Test a pixel of the mip level 0 of a 2D texture.
|
||||
#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \
|
||||
AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \
|
||||
|
|
|
@ -27,8 +27,8 @@ class MultisampledRenderingTest : public DawnTest {
|
|||
}
|
||||
|
||||
void InitTexturesForTest() {
|
||||
mMultisampledColorView =
|
||||
CreateTextureForOutputAttachment(kColorFormat, kSampleCount).CreateView();
|
||||
mMultisampledColorTexture = CreateTextureForOutputAttachment(kColorFormat, kSampleCount);
|
||||
mMultisampledColorView = mMultisampledColorTexture.CreateView();
|
||||
mResolveTexture = CreateTextureForOutputAttachment(kColorFormat, 1);
|
||||
mResolveView = mResolveTexture.CreateView();
|
||||
|
||||
|
@ -173,6 +173,7 @@ class MultisampledRenderingTest : public DawnTest {
|
|||
constexpr static wgpu::TextureFormat kDepthStencilFormat =
|
||||
wgpu::TextureFormat::Depth24PlusStencil8;
|
||||
|
||||
wgpu::Texture mMultisampledColorTexture;
|
||||
wgpu::TextureView mMultisampledColorView;
|
||||
wgpu::Texture mResolveTexture;
|
||||
wgpu::TextureView mResolveView;
|
||||
|
@ -242,6 +243,34 @@ TEST_P(MultisampledRenderingTest, ResolveInto2DTexture) {
|
|||
VerifyResolveTarget(kGreen, mResolveTexture);
|
||||
}
|
||||
|
||||
// Test that a single-layer multisampled texture view can be created and resolved from.
|
||||
TEST_P(MultisampledRenderingTest, ResolveFromSingleLayerArrayInto2DTexture) {
|
||||
constexpr bool kTestDepth = false;
|
||||
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPipeline pipeline = CreateRenderPipelineWithOneOutputForTest(kTestDepth);
|
||||
|
||||
constexpr wgpu::Color kGreen = {0.0f, 0.8f, 0.0f, 0.8f};
|
||||
constexpr uint32_t kSize = sizeof(kGreen);
|
||||
|
||||
// Draw a green triangle.
|
||||
{
|
||||
wgpu::TextureViewDescriptor desc = {};
|
||||
desc.dimension = wgpu::TextureViewDimension::e2DArray;
|
||||
desc.arrayLayerCount = 1;
|
||||
|
||||
utils::ComboRenderPassDescriptor renderPass = CreateComboRenderPassDescriptorForTest(
|
||||
{mMultisampledColorTexture.CreateView(&desc)}, {mResolveView}, wgpu::LoadOp::Clear,
|
||||
wgpu::LoadOp::Clear, kTestDepth);
|
||||
|
||||
EncodeRenderPassForTest(commandEncoder, renderPass, pipeline, &kGreen.r, kSize);
|
||||
}
|
||||
|
||||
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
|
||||
queue.Submit(1, &commandBuffer);
|
||||
|
||||
VerifyResolveTarget(kGreen, mResolveTexture);
|
||||
}
|
||||
|
||||
// Test multisampled rendering with depth test works correctly.
|
||||
TEST_P(MultisampledRenderingTest, MultisampledRenderingWithDepthTest) {
|
||||
constexpr bool kTestDepth = true;
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2020 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "tests/DawnTest.h"
|
||||
|
||||
#include "common/Math.h"
|
||||
#include "utils/ComboRenderPipelineDescriptor.h"
|
||||
#include "utils/WGPUHelpers.h"
|
||||
|
||||
namespace {
|
||||
// https://github.com/gpuweb/gpuweb/issues/108
|
||||
// Vulkan, Metal, and D3D11 have the same standard multisample pattern. D3D12 is the same as
|
||||
// D3D11 but it was left out of the documentation.
|
||||
// {0.375, 0.125}, {0.875, 0.375}, {0.125 0.625}, {0.625, 0.875}
|
||||
// In this test, we store them in -1 to 1 space because it makes it
|
||||
// simpler to upload vertex data. Y is flipped because there is a flip between clip space and
|
||||
// rasterization space.
|
||||
static constexpr std::array<std::array<float, 2>, 4> kSamplePositions = {
|
||||
{{0.375 * 2 - 1, 1 - 0.125 * 2},
|
||||
{0.875 * 2 - 1, 1 - 0.375 * 2},
|
||||
{0.125 * 2 - 1, 1 - 0.625 * 2},
|
||||
{0.625 * 2 - 1, 1 - 0.875 * 2}}};
|
||||
} // anonymous namespace
|
||||
|
||||
class MultisampledSamplingTest : public DawnTest {
|
||||
protected:
|
||||
static constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::R8Unorm;
|
||||
static constexpr wgpu::TextureFormat kDepthFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
static constexpr wgpu::TextureFormat kDepthOutFormat = wgpu::TextureFormat::R32Float;
|
||||
static constexpr uint32_t kSampleCount = 4;
|
||||
|
||||
// Render pipeline for drawing to a multisampled color and depth attachment.
|
||||
wgpu::RenderPipeline drawPipeline;
|
||||
|
||||
// A compute pipeline to texelFetch the sample locations and output the results to a buffer.
|
||||
wgpu::ComputePipeline checkSamplePipeline;
|
||||
|
||||
void SetUp() override {
|
||||
DawnTest::SetUp();
|
||||
{
|
||||
utils::ComboRenderPipelineDescriptor desc(device);
|
||||
|
||||
desc.vertexStage.module =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex,
|
||||
R"(#version 450
|
||||
layout(location=0) in vec2 pos;
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
})");
|
||||
|
||||
desc.cFragmentStage.module =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment,
|
||||
R"(#version 450
|
||||
layout(location = 0) out float fragColor;
|
||||
void main() {
|
||||
fragColor = 1.0;
|
||||
gl_FragDepth = 0.7;
|
||||
})");
|
||||
|
||||
desc.cVertexState.vertexBufferCount = 1;
|
||||
desc.cVertexState.cVertexBuffers[0].attributeCount = 1;
|
||||
desc.cVertexState.cVertexBuffers[0].arrayStride = 2 * sizeof(float);
|
||||
desc.cVertexState.cAttributes[0].format = wgpu::VertexFormat::Float2;
|
||||
|
||||
desc.cDepthStencilState.format = kDepthFormat;
|
||||
desc.cDepthStencilState.depthWriteEnabled = true;
|
||||
desc.depthStencilState = &desc.cDepthStencilState;
|
||||
|
||||
desc.sampleCount = kSampleCount;
|
||||
desc.colorStateCount = 1;
|
||||
desc.cColorStates[0].format = kColorFormat;
|
||||
|
||||
desc.primitiveTopology = wgpu::PrimitiveTopology::TriangleStrip;
|
||||
|
||||
drawPipeline = device.CreateRenderPipeline(&desc);
|
||||
}
|
||||
{
|
||||
wgpu::ComputePipelineDescriptor desc = {};
|
||||
desc.computeStage.entryPoint = "main";
|
||||
desc.computeStage.module =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute,
|
||||
R"(#version 450
|
||||
layout(set = 0, binding = 0) uniform sampler sampler0;
|
||||
layout(set = 0, binding = 1) uniform texture2DMS texture0;
|
||||
layout(set = 0, binding = 2) uniform texture2DMS texture1;
|
||||
|
||||
layout(set = 0, binding = 3, std430) buffer Results {
|
||||
float colorSamples[4];
|
||||
float depthSamples[4];
|
||||
};
|
||||
|
||||
void main() {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
colorSamples[i] =
|
||||
texelFetch(sampler2DMS(texture0, sampler0), ivec2(0, 0), i).x;
|
||||
|
||||
depthSamples[i] =
|
||||
texelFetch(sampler2DMS(texture1, sampler0), ivec2(0, 0), i).x;
|
||||
}
|
||||
})");
|
||||
|
||||
checkSamplePipeline = device.CreateComputePipeline(&desc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Test that the multisampling sample positions are correct. This test works by drawing a
|
||||
// thin quad multiple times from left to right and from top to bottom on a 1x1 canvas.
|
||||
// Each time, the quad should cover a single sample position.
|
||||
// After drawing, a compute shader fetches all of the samples (both color and depth),
|
||||
// and we check that only the one covered has data.
|
||||
// We "scan" the vertical and horizontal dimensions separately to check that the triangle
|
||||
// must cover both the X and Y coordinates of the sample position (no false positives if
|
||||
// it covers the X position but not the Y, or vice versa).
|
||||
TEST_P(MultisampledSamplingTest, SamplePositions) {
|
||||
static constexpr wgpu::Extent3D kTextureSize = {1, 1, 1};
|
||||
|
||||
wgpu::Texture colorTexture;
|
||||
{
|
||||
wgpu::TextureDescriptor desc = {};
|
||||
desc.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment;
|
||||
desc.size = kTextureSize;
|
||||
desc.format = kColorFormat;
|
||||
desc.sampleCount = kSampleCount;
|
||||
colorTexture = device.CreateTexture(&desc);
|
||||
}
|
||||
|
||||
wgpu::Texture depthTexture;
|
||||
{
|
||||
wgpu::TextureDescriptor desc = {};
|
||||
desc.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment;
|
||||
desc.size = kTextureSize;
|
||||
desc.format = kDepthFormat;
|
||||
desc.sampleCount = kSampleCount;
|
||||
depthTexture = device.CreateTexture(&desc);
|
||||
}
|
||||
|
||||
static constexpr float kQuadWidth = 0.075;
|
||||
std::vector<float> vBufferData;
|
||||
|
||||
// Add vertices for vertical quads
|
||||
for (uint32_t s = 0; s < kSampleCount; ++s) {
|
||||
// clang-format off
|
||||
vBufferData.insert(vBufferData.end(), {
|
||||
kSamplePositions[s][0] - kQuadWidth, -1.0,
|
||||
kSamplePositions[s][0] - kQuadWidth, 1.0,
|
||||
kSamplePositions[s][0] + kQuadWidth, -1.0,
|
||||
kSamplePositions[s][0] + kQuadWidth, 1.0,
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Add vertices for horizontal quads
|
||||
for (uint32_t s = 0; s < kSampleCount; ++s) {
|
||||
// clang-format off
|
||||
vBufferData.insert(vBufferData.end(), {
|
||||
-1.0, kSamplePositions[s][1] - kQuadWidth,
|
||||
-1.0, kSamplePositions[s][1] + kQuadWidth,
|
||||
1.0, kSamplePositions[s][1] - kQuadWidth,
|
||||
1.0, kSamplePositions[s][1] + kQuadWidth,
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
wgpu::Buffer vBuffer = utils::CreateBufferFromData(
|
||||
device, vBufferData.data(), static_cast<uint32_t>(vBufferData.size() * sizeof(float)),
|
||||
wgpu::BufferUsage::Vertex);
|
||||
|
||||
static constexpr uint32_t kQuadNumBytes = 8 * sizeof(float);
|
||||
|
||||
wgpu::SamplerDescriptor samplerDesc = {};
|
||||
wgpu::Sampler sampler = device.CreateSampler(&samplerDesc);
|
||||
wgpu::TextureView colorView = colorTexture.CreateView();
|
||||
wgpu::TextureView depthView = depthTexture.CreateView();
|
||||
|
||||
static constexpr uint64_t kResultSize = 4 * sizeof(float) + 4 * sizeof(float);
|
||||
uint64_t alignedResultSize = Align(kResultSize, 256);
|
||||
|
||||
wgpu::BufferDescriptor outputBufferDesc = {};
|
||||
outputBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
|
||||
outputBufferDesc.size = alignedResultSize * 8;
|
||||
wgpu::Buffer outputBuffer = device.CreateBuffer(&outputBufferDesc);
|
||||
|
||||
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||
for (uint32_t iter = 0; iter < 2; ++iter) {
|
||||
for (uint32_t sample = 0; sample < kSampleCount; ++sample) {
|
||||
uint32_t sampleOffset = (iter * kSampleCount + sample);
|
||||
|
||||
utils::ComboRenderPassDescriptor renderPass({colorView}, depthView);
|
||||
renderPass.cDepthStencilAttachmentInfo.clearDepth = 0.f;
|
||||
|
||||
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
|
||||
renderPassEncoder.SetPipeline(drawPipeline);
|
||||
renderPassEncoder.SetVertexBuffer(0, vBuffer, kQuadNumBytes * sampleOffset,
|
||||
kQuadNumBytes);
|
||||
renderPassEncoder.Draw(4);
|
||||
renderPassEncoder.EndPass();
|
||||
|
||||
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||
computePassEncoder.SetPipeline(checkSamplePipeline);
|
||||
computePassEncoder.SetBindGroup(
|
||||
0, utils::MakeBindGroup(
|
||||
device, checkSamplePipeline.GetBindGroupLayout(0),
|
||||
{{0, sampler},
|
||||
{1, colorView},
|
||||
{2, depthView},
|
||||
{3, outputBuffer, alignedResultSize * sampleOffset, kResultSize}}));
|
||||
computePassEncoder.Dispatch(1);
|
||||
computePassEncoder.EndPass();
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
|
||||
queue.Submit(1, &commandBuffer);
|
||||
|
||||
std::array<float, 8> expectedData;
|
||||
|
||||
expectedData = {1, 0, 0, 0, 0.7, 0, 0, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 0 * alignedResultSize, 8)
|
||||
<< "vertical sample 0";
|
||||
|
||||
expectedData = {0, 1, 0, 0, 0, 0.7, 0, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 1 * alignedResultSize, 8)
|
||||
<< "vertical sample 1";
|
||||
|
||||
expectedData = {0, 0, 1, 0, 0, 0, 0.7, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 2 * alignedResultSize, 8)
|
||||
<< "vertical sample 2";
|
||||
|
||||
expectedData = {0, 0, 0, 1, 0, 0, 0, 0.7};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 3 * alignedResultSize, 8)
|
||||
<< "vertical sample 3";
|
||||
|
||||
expectedData = {1, 0, 0, 0, 0.7, 0, 0, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 4 * alignedResultSize, 8)
|
||||
<< "horizontal sample 0";
|
||||
|
||||
expectedData = {0, 1, 0, 0, 0, 0.7, 0, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 5 * alignedResultSize, 8)
|
||||
<< "horizontal sample 1";
|
||||
|
||||
expectedData = {0, 0, 1, 0, 0, 0, 0.7, 0};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 6 * alignedResultSize, 8)
|
||||
<< "horizontal sample 2";
|
||||
|
||||
expectedData = {0, 0, 0, 1, 0, 0, 0, 0.7};
|
||||
EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data(), outputBuffer, 7 * alignedResultSize, 8)
|
||||
<< "horizontal sample 3";
|
||||
}
|
||||
|
||||
DAWN_INSTANTIATE_TEST(MultisampledSamplingTest,
|
||||
D3D12Backend(),
|
||||
MetalBackend(),
|
||||
OpenGLBackend(),
|
||||
VulkanBackend());
|
|
@ -727,6 +727,50 @@ TEST_F(BindGroupLayoutValidationTest, DynamicBufferNumberLimit) {
|
|||
}
|
||||
}
|
||||
|
||||
TEST_F(BindGroupLayoutValidationTest, MultisampledTextures) {
|
||||
// Multisampled 2D texture works.
|
||||
utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::e2D},
|
||||
});
|
||||
|
||||
// Multisampled 2D (defaulted) texture works.
|
||||
utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::Undefined},
|
||||
});
|
||||
|
||||
// Multisampled 2D array texture is invalid.
|
||||
ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::e2DArray},
|
||||
}));
|
||||
|
||||
// Multisampled cube texture is invalid.
|
||||
ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::Cube},
|
||||
}));
|
||||
|
||||
// Multisampled cube array texture is invalid.
|
||||
ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::CubeArray},
|
||||
}));
|
||||
|
||||
// Multisampled 3D texture is invalid.
|
||||
ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(
|
||||
device, {
|
||||
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false, true,
|
||||
wgpu::TextureViewDimension::e3D},
|
||||
}));
|
||||
}
|
||||
|
||||
constexpr uint64_t kBufferSize = 3 * kMinDynamicBufferOffsetAlignment + 8;
|
||||
constexpr uint32_t kBindingSize = 9;
|
||||
|
||||
|
@ -1364,7 +1408,10 @@ class BindGroupLayoutCompatibilityTest : public ValidationTest {
|
|||
return device.CreateBuffer(&bufferDescriptor);
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline CreateRenderPipeline(std::vector<wgpu::BindGroupLayout> bindGroupLayout) {
|
||||
wgpu::RenderPipeline CreateFSRenderPipeline(
|
||||
const char* fsShader,
|
||||
std::vector<wgpu::BindGroupLayout> bindGroupLayout) {
|
||||
|
||||
wgpu::ShaderModule vsModule =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
|
||||
#version 450
|
||||
|
@ -1372,17 +1419,7 @@ class BindGroupLayoutCompatibilityTest : public ValidationTest {
|
|||
})");
|
||||
|
||||
wgpu::ShaderModule fsModule =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
|
||||
#version 450
|
||||
layout(std140, set = 0, binding = 0) buffer SBuffer {
|
||||
vec2 value2;
|
||||
} sBuffer;
|
||||
layout(std140, set = 1, binding = 0) readonly buffer RBuffer {
|
||||
vec2 value3;
|
||||
} rBuffer;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
})");
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fsShader);
|
||||
|
||||
wgpu::PipelineLayoutDescriptor descriptor;
|
||||
descriptor.bindGroupLayoutCount = bindGroupLayout.size();
|
||||
|
@ -1395,23 +1432,26 @@ class BindGroupLayoutCompatibilityTest : public ValidationTest {
|
|||
return device.CreateRenderPipeline(&pipelineDescriptor);
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline CreateRenderPipeline(std::vector<wgpu::BindGroupLayout> bindGroupLayout) {
|
||||
return CreateFSRenderPipeline(R"(
|
||||
#version 450
|
||||
layout(std140, set = 0, binding = 0) buffer SBuffer {
|
||||
vec2 value2;
|
||||
} sBuffer;
|
||||
layout(std140, set = 1, binding = 0) readonly buffer RBuffer {
|
||||
vec2 value3;
|
||||
} rBuffer;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
})",
|
||||
std::move(bindGroupLayout));
|
||||
}
|
||||
|
||||
wgpu::ComputePipeline CreateComputePipeline(
|
||||
const char* shader,
|
||||
std::vector<wgpu::BindGroupLayout> bindGroupLayout) {
|
||||
wgpu::ShaderModule csModule =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
|
||||
#version 450
|
||||
const uint kTileSize = 4;
|
||||
const uint kInstances = 11;
|
||||
|
||||
layout(local_size_x = kTileSize, local_size_y = kTileSize, local_size_z = 1) in;
|
||||
layout(std140, set = 0, binding = 0) buffer SBuffer {
|
||||
float value2;
|
||||
} dst;
|
||||
layout(std140, set = 1, binding = 0) readonly buffer RBuffer {
|
||||
readonly float value3;
|
||||
} rdst;
|
||||
void main() {
|
||||
})");
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, shader);
|
||||
|
||||
wgpu::PipelineLayoutDescriptor descriptor;
|
||||
descriptor.bindGroupLayoutCount = bindGroupLayout.size();
|
||||
|
@ -1425,6 +1465,25 @@ class BindGroupLayoutCompatibilityTest : public ValidationTest {
|
|||
|
||||
return device.CreateComputePipeline(&csDesc);
|
||||
}
|
||||
|
||||
wgpu::ComputePipeline CreateComputePipeline(
|
||||
std::vector<wgpu::BindGroupLayout> bindGroupLayout) {
|
||||
return CreateComputePipeline(R"(
|
||||
#version 450
|
||||
const uint kTileSize = 4;
|
||||
const uint kInstances = 11;
|
||||
|
||||
layout(local_size_x = kTileSize, local_size_y = kTileSize, local_size_z = 1) in;
|
||||
layout(std140, set = 0, binding = 0) buffer SBuffer {
|
||||
float value2;
|
||||
} dst;
|
||||
layout(std140, set = 1, binding = 0) readonly buffer RBuffer {
|
||||
readonly float value3;
|
||||
} rdst;
|
||||
void main() {
|
||||
})",
|
||||
std::move(bindGroupLayout));
|
||||
}
|
||||
};
|
||||
|
||||
// Test that it is valid to pass a writable storage buffer in the pipeline layout when the shader
|
||||
|
@ -1459,6 +1518,76 @@ TEST_F(BindGroupLayoutCompatibilityTest, ROStorageInBGLWithRWStorageInShader) {
|
|||
ASSERT_DEVICE_ERROR(CreateComputePipeline({bgl0, bgl1}));
|
||||
}
|
||||
|
||||
TEST_F(BindGroupLayoutCompatibilityTest, TextureViewDimension) {
|
||||
constexpr char kTexture2DShader[] = R"(
|
||||
#version 450
|
||||
layout(set = 0, binding = 0) uniform texture2D texture;
|
||||
void main() {
|
||||
})";
|
||||
|
||||
// Render: Test that 2D texture with 2D view dimension works
|
||||
CreateFSRenderPipeline(
|
||||
kTexture2DShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2D}})});
|
||||
|
||||
// Render: Test that 2D texture with 2D array view dimension is invalid
|
||||
ASSERT_DEVICE_ERROR(CreateFSRenderPipeline(
|
||||
kTexture2DShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2DArray}})}));
|
||||
|
||||
// Compute: Test that 2D texture with 2D view dimension works
|
||||
CreateComputePipeline(
|
||||
kTexture2DShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2D}})});
|
||||
|
||||
// Compute: Test that 2D texture with 2D array view dimension is invalid
|
||||
ASSERT_DEVICE_ERROR(CreateComputePipeline(
|
||||
kTexture2DShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2DArray}})}));
|
||||
|
||||
constexpr char kTexture2DArrayShader[] = R"(
|
||||
#version 450
|
||||
layout(set = 0, binding = 0) uniform texture2DArray texture;
|
||||
void main() {
|
||||
})";
|
||||
|
||||
// Render: Test that 2D texture array with 2D array view dimension works
|
||||
CreateFSRenderPipeline(
|
||||
kTexture2DArrayShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2DArray}})});
|
||||
|
||||
// Render: Test that 2D texture array with 2D view dimension is invalid
|
||||
ASSERT_DEVICE_ERROR(CreateFSRenderPipeline(
|
||||
kTexture2DArrayShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2D}})}));
|
||||
|
||||
// Compute: Test that 2D texture array with 2D array view dimension works
|
||||
CreateComputePipeline(
|
||||
kTexture2DArrayShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2DArray}})});
|
||||
|
||||
// Compute: Test that 2D texture array with 2D view dimension is invalid
|
||||
ASSERT_DEVICE_ERROR(CreateComputePipeline(
|
||||
kTexture2DArrayShader,
|
||||
{utils::MakeBindGroupLayout(
|
||||
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture, false,
|
||||
false, wgpu::TextureViewDimension::e2D}})}));
|
||||
}
|
||||
|
||||
class BindingsValidationTest : public BindGroupLayoutCompatibilityTest {
|
||||
public:
|
||||
void TestRenderPassBindings(const wgpu::BindGroup* bg,
|
||||
|
|
|
@ -256,9 +256,6 @@ TEST_F(GetBindGroupLayoutTests, Multisampled) {
|
|||
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
|
||||
}
|
||||
|
||||
// TODO: Support multisampling
|
||||
GTEST_SKIP() << "Multisampling unimplemented";
|
||||
#if 0
|
||||
{
|
||||
binding.multisampled = true;
|
||||
wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"(
|
||||
|
@ -268,7 +265,6 @@ TEST_F(GetBindGroupLayoutTests, Multisampled) {
|
|||
void main() {})");
|
||||
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Test that texture view dimension matches the shader.
|
||||
|
|
|
@ -486,3 +486,29 @@ TEST_F(RenderPipelineValidationTest, StorageBufferInVertexShaderNoLayout) {
|
|||
descriptor.cFragmentStage.module = fsModule;
|
||||
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
|
||||
}
|
||||
|
||||
// Test that a pipeline with defaulted layout may not have multisampled array textures
|
||||
// TODO(enga): Also test multisampled cube, cube array, and 3D. These have no GLSL keywords.
|
||||
TEST_F(RenderPipelineValidationTest, MultisampledTexture) {
|
||||
utils::ComboRenderPipelineDescriptor descriptor(device);
|
||||
descriptor.layout = nullptr;
|
||||
descriptor.cFragmentStage.module = fsModule;
|
||||
|
||||
// Base case works.
|
||||
descriptor.vertexStage.module =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
|
||||
#version 450
|
||||
layout(set = 0, binding = 0) uniform texture2DMS texture;
|
||||
void main() {
|
||||
})");
|
||||
device.CreateRenderPipeline(&descriptor);
|
||||
|
||||
// texture2DMSArray invalid
|
||||
descriptor.vertexStage.module =
|
||||
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
|
||||
#version 450
|
||||
layout(set = 0, binding = 0) uniform texture2DMSArray texture;
|
||||
void main() {
|
||||
})");
|
||||
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue