dawn-cmake/src/tests/end2end/InputStateTests.cpp
Corentin Wallez 6f7749cce9 Change render passes from multi to single pass.
This as an API change to get closer to the direction in which WebGPU is
headed. The API change in next.json caused a ton of files to be changed
in the same commit to keep things compiling.

API: the Framebuffer and RenderPass objects are now merged in a single
RenderPassInfo that contains the attachments, loadOps and clear values
for a BeginRenderPass command. The concept of subpass is removed.
The RenderPass creation argument to RenderPipelines is replaced by
explicitly setting the format of attachments for RenderPipeline.

Validation: SetPipeline checks are changed to check that the attachments
info set on a RenderPipeline matches the attachments of the render pass.

Backends: Most changes are simplifications of the backends that no
longer require and indirection to query the current subpass out of the
render pass in BeginSubpass, and don't need to get the attachment info
from a RenderPass when creating RenderPipelines. In the Vulkan backend,
a VkRenderPass cache is added to reuse VkRenderPasses between
RenderPassInfos and RenderPipelines.

Tests and examples: they are updated with the simplified API. Tests
specific to the Framebuffer and RenderPass objects were removed and
validation tests for RenderPassInfo were added.

Tested by running CppHelloTriangle on all backends, end2end tests on all
platforms and all examples on the GL backend.
2018-05-16 11:18:14 -04:00

425 lines
16 KiB
C++

// Copyright 2017 The NXT 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/NXTTest.h"
#include "common/Assert.h"
#include "utils/NXTHelpers.h"
using nxt::InputStepMode;
using nxt::VertexFormat;
// Input state tests all work the same way: the test will render triangles in a grid up to 4x4. Each triangle
// is position in the grid such that X will correspond to the "triangle number" and the Y to the instance number.
// Each test will set up an input state and buffers, and the vertex shader will check that the vertex attributes
// corresponds to predetermined values. On success it outputs green, otherwise red.
//
// The predetermined values are "K * gl_VertexID + componentIndex" for vertex-indexed buffers, and
// "K * gl_InstanceID + componentIndex" for instance-indexed buffers.
constexpr static unsigned int kRTSize = 400;
constexpr static unsigned int kRTCellOffset = 50;
constexpr static unsigned int kRTCellSize = 100;
class InputStateTest : public NXTTest {
protected:
void SetUp() override {
NXTTest::SetUp();
renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
}
bool ShouldComponentBeDefault(VertexFormat format, int component) {
EXPECT_TRUE(component >= 0 && component < 4);
switch (format) {
case VertexFormat::FloatR32G32B32A32:
case VertexFormat::UnormR8G8B8A8:
return component >= 4;
case VertexFormat::FloatR32G32B32:
return component >= 3;
case VertexFormat::FloatR32G32:
case VertexFormat::UnormR8G8:
return component >= 2;
case VertexFormat::FloatR32:
return component >= 1;
default:
NXT_UNREACHABLE();
}
}
struct ShaderTestSpec {
uint32_t location;
VertexFormat format;
InputStepMode step;
};
nxt::RenderPipeline MakeTestPipeline(const nxt::InputState& inputState, int multiplier, std::vector<ShaderTestSpec> testSpec) {
std::ostringstream vs;
vs << "#version 450\n";
// TODO(cwallez@chromium.org): this only handles float attributes, we should extend it to other types
// Adds line of the form
// layout(location=1) in vec4 input1;
for (const auto& input : testSpec) {
vs << "layout(location=" << input.location << ") in vec4 input" << input.location << ";\n";
}
vs << "layout(location = 0) out vec4 color;\n";
vs << "void main() {\n";
// Hard code the triangle in the shader so that we don't have to add a vertex input for it.
// Also this places the triangle in the grid based on its VertexID and InstanceID
vs << " const vec2 pos[3] = vec2[3](vec2(0.5f, 1.0f), vec2(0.0f, 0.0f), vec2(1.0f, 0.0f));\n";
vs << " vec2 offset = vec2(float(gl_VertexIndex / 3), float(gl_InstanceIndex));\n";
vs << " vec2 worldPos = pos[gl_VertexIndex % 3] + offset;\n";
vs << " gl_Position = vec4(worldPos / 2 - vec2(1.0f), 0.0f, 1.0f);\n";
// Perform the checks by successively ANDing a boolean
vs << " bool success = true;\n";
for (const auto& input : testSpec) {
for (int component = 0; component < 4; ++component) {
vs << " success = success && (input" << input.location << "[" << component << "] == ";
if (ShouldComponentBeDefault(input.format, component)) {
vs << (component == 3 ? "1.0f" : "0.0f");
} else {
if (input.step == InputStepMode::Vertex) {
vs << multiplier << " * gl_VertexIndex + " << component << ".0f";
} else {
vs << multiplier << " * gl_InstanceIndex + " << component << ".0f";
}
}
vs << ");\n";
}
}
// Choose the color
vs << " if (success) {\n";
vs << " color = vec4(0.0f, 1.0f, 0.0f, 1.0f);\n";
vs << " } else {\n";
vs << " color = vec4(1.0f, 0.0f, 0.0f, 1.0f);\n";
vs << " }\n;";
vs << "}\n";
nxt::ShaderModule vsModule = utils::CreateShaderModule(device, nxt::ShaderStage::Vertex, vs.str().c_str());
nxt::ShaderModule fsModule = utils::CreateShaderModule(device, nxt::ShaderStage::Fragment, R"(
#version 450
layout(location = 0) in vec4 color;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = color;
})"
);
return device.CreateRenderPipelineBuilder()
.SetColorAttachmentFormat(0, renderPass.colorFormat)
.SetStage(nxt::ShaderStage::Vertex, vsModule, "main")
.SetStage(nxt::ShaderStage::Fragment, fsModule, "main")
.SetInputState(inputState)
.GetResult();
}
struct InputSpec {
uint32_t slot;
uint32_t stride;
InputStepMode step;
};
struct AttributeSpec {
uint32_t location;
uint32_t slot;
uint32_t offset;
VertexFormat format;
};
nxt::InputState MakeInputState(std::vector<InputSpec> inputs, std::vector<AttributeSpec> attributes) {
nxt::InputStateBuilder builder = device.CreateInputStateBuilder();
for (const auto& input : inputs) {
builder.SetInput(input.slot, input.stride, input.step);
}
for (const auto& attribute : attributes) {
builder.SetAttribute(attribute.location, attribute.slot, attribute.format, attribute.offset);
}
return builder.GetResult();
}
template<typename T>
nxt::Buffer MakeVertexBuffer(std::vector<T> data) {
return utils::CreateFrozenBufferFromData(device, data.data(), static_cast<uint32_t>(data.size() * sizeof(T)), nxt::BufferUsageBit::Vertex);
}
struct DrawVertexBuffer {
uint32_t location;
nxt::Buffer* buffer;
};
void DoTestDraw(const nxt::RenderPipeline& pipeline, unsigned int triangles, unsigned int instances, std::vector<DrawVertexBuffer> vertexBuffers) {
EXPECT_LE(triangles, 4u);
EXPECT_LE(instances, 4u);
nxt::CommandBufferBuilder builder = device.CreateCommandBufferBuilder();
renderPass.color.TransitionUsage(nxt::TextureUsageBit::OutputAttachment);
builder.BeginRenderPass(renderPass.renderPassInfo)
.SetRenderPipeline(pipeline);
uint32_t zeroOffset = 0;
for (const auto& buffer : vertexBuffers) {
builder.SetVertexBuffers(buffer.location, 1, buffer.buffer, &zeroOffset);
}
nxt::CommandBuffer commands = builder
.DrawArrays(triangles * 3, instances, 0, 0)
.EndRenderPass()
.GetResult();
queue.Submit(1, &commands);
// Check that the center of each triangle is pure green, so that if a single vertex shader
// instance fails, linear interpolation makes the pixel check fail.
for (unsigned int triangle = 0; triangle < 4; triangle++) {
for (unsigned int instance = 0; instance < 4; instance++) {
unsigned int x = kRTCellOffset + kRTCellSize * triangle;
unsigned int y = kRTCellOffset + kRTCellSize * instance;
if (triangle < triangles && instance < instances) {
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), renderPass.color, x, y);
} else {
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), renderPass.color, x, y);
}
}
}
}
utils::BasicRenderPass renderPass;
};
// Test compilation and usage of the fixture :)
TEST_P(InputStateTest, Basic) {
nxt::InputState inputState = MakeInputState({
{0, 4 * sizeof(float), InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3,
1, 2, 3, 4,
2, 3, 4, 5
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// Test a stride of 0 works
TEST_P(InputStateTest, ZeroStride) {
nxt::InputState inputState = MakeInputState({
{0, 0, InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 0, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3,
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// Test attributes defaults to (0, 0, 0, 1) if the input state doesn't have all components
TEST_P(InputStateTest, AttributeExpanding) {
// R32F case
{
nxt::InputState inputState = MakeInputState({
{0, 0, InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 0, {
{0, VertexFormat::FloatR32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// RG32F case
{
nxt::InputState inputState = MakeInputState({
{0, 0, InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 0, {
{0, VertexFormat::FloatR32G32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// RGB32F case
{
nxt::InputState inputState = MakeInputState({
{0, 0, InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 0, {
{0, VertexFormat::FloatR32G32B32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
}
// Test a stride larger than the attributes
TEST_P(InputStateTest, StrideLargerThanAttributes) {
nxt::InputState inputState = MakeInputState({
{0, 8 * sizeof(float), InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3, 0, 0, 0, 0,
1, 2, 3, 4, 0, 0, 0, 0,
2, 3, 4, 5, 0, 0, 0, 0,
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// Test two attributes at an offset, vertex version
TEST_P(InputStateTest, TwoAttributesAtAnOffsetVertex) {
nxt::InputState inputState = MakeInputState({
{0, 8 * sizeof(float), InputStepMode::Vertex}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32},
{1, 0, 4 * sizeof(float), VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Vertex}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3, 0, 1, 2, 3,
1, 2, 3, 4, 1, 2, 3, 4,
2, 3, 4, 5, 2, 3, 4, 5,
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// Test two attributes at an offset, instance version
TEST_P(InputStateTest, TwoAttributesAtAnOffsetInstance) {
nxt::InputState inputState = MakeInputState({
{0, 8 * sizeof(float), InputStepMode::Instance}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32},
{1, 0, 4 * sizeof(float), VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Instance}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3, 0, 1, 2, 3,
1, 2, 3, 4, 1, 2, 3, 4,
2, 3, 4, 5, 2, 3, 4, 5,
});
DoTestDraw(pipeline, 1, 1, {DrawVertexBuffer{0, &buffer0}});
}
// Test a pure-instance input state
TEST_P(InputStateTest, PureInstance) {
nxt::InputState inputState = MakeInputState({
{0, 4 * sizeof(float), InputStepMode::Instance}
}, {
{0, 0, 0, VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32G32B32A32, InputStepMode::Instance}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3,
1, 2, 3, 4,
2, 3, 4, 5,
3, 4, 5, 6,
});
DoTestDraw(pipeline, 1, 4, {DrawVertexBuffer{0, &buffer0}});
}
// Test with mixed everything, vertex vs. instance, different stride and offsets
// different attribute types
TEST_P(InputStateTest, MixedEverything) {
nxt::InputState inputState = MakeInputState({
{0, 12 * sizeof(float), InputStepMode::Vertex},
{1, 10 * sizeof(float), InputStepMode::Instance},
}, {
{0, 0, 0, VertexFormat::FloatR32},
{1, 0, 6 * sizeof(float), VertexFormat::FloatR32G32},
{2, 1, 0, VertexFormat::FloatR32G32B32},
{3, 1, 5 * sizeof(float), VertexFormat::FloatR32G32B32A32}
}
);
nxt::RenderPipeline pipeline = MakeTestPipeline(inputState, 1, {
{0, VertexFormat::FloatR32, InputStepMode::Vertex},
{1, VertexFormat::FloatR32G32, InputStepMode::Vertex},
{2, VertexFormat::FloatR32G32B32, InputStepMode::Instance},
{3, VertexFormat::FloatR32G32B32A32, InputStepMode::Instance}
});
nxt::Buffer buffer0 = MakeVertexBuffer<float>({
0, 1, 2, 3, 0, 0, 0, 1, 2, 3, 0, 0,
1, 2, 3, 4, 0, 0, 1, 2, 3, 4, 0, 0,
2, 3, 4, 5, 0, 0, 2, 3, 4, 5, 0, 0,
3, 4, 5, 6, 0, 0, 3, 4, 5, 6, 0, 0,
});
nxt::Buffer buffer1 = MakeVertexBuffer<float>({
0, 1, 2, 3, 0, 0, 1, 2, 3, 0,
1, 2, 3, 4, 0, 1, 2, 3, 4, 0,
2, 3, 4, 5, 0, 2, 3, 4, 5, 0,
3, 4, 5, 6, 0, 3, 4, 5, 6, 0,
});
DoTestDraw(pipeline, 1, 1, {{0, &buffer0}, {1, &buffer1}});
}
NXT_INSTANTIATE_TEST(InputStateTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)
// TODO for the input state:
// - Add more vertex formats
// - Add checks that the stride is enough to contain all attributes
// - Add checks stride less than some limit
// - Add checks for alignement of vertex buffers and attributes if needed
// - Check for attribute narrowing
// - Check that the input state and the pipeline vertex input types match