mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-09-03 09:18:41 +00:00
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.
425 lines
16 KiB
C++
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
|