blob: 0bd371c366660c0aaf9f43bbbccc599769f918e3 [file] [log] [blame]
// Copyright 2017 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/Assert.h"
#include "common/Constants.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/DawnHelpers.h"
#include <array>
class PushConstantTest: public DawnTest {
protected:
// Layout, bind group and friends to store results for compute tests, can have an extra buffer
// so that two different pipeline layout can be created.
struct TestBindings {
dawn::PipelineLayout layout;
dawn::BindGroup bindGroup;
dawn::Buffer resultBuffer;
};
TestBindings MakeTestBindings(bool extraBuffer) {
uint32_t one = 1;
dawn::Buffer buf1 = utils::CreateBufferFromData(device, &one, 4, dawn::BufferUsageBit::Storage |
dawn::BufferUsageBit::TransferSrc |
dawn::BufferUsageBit::TransferDst);
dawn::BufferDescriptor buf2Desc;
buf2Desc.size = 4;
buf2Desc.usage = dawn::BufferUsageBit::Storage;
dawn::Buffer buf2 = device.CreateBuffer(&buf2Desc);
dawn::ShaderStageBit kAllStages = dawn::ShaderStageBit::Compute | dawn::ShaderStageBit::Fragment | dawn::ShaderStageBit::Vertex;
constexpr dawn::ShaderStageBit kNoStages{};
dawn::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device,
{
{0, kAllStages, dawn::BindingType::StorageBuffer},
{1, extraBuffer ? kAllStages : kNoStages, dawn::BindingType::StorageBuffer},
});
dawn::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl);
dawn::BindGroup bg;
if (extraBuffer) {
bg = utils::MakeBindGroup(device, bgl, {
{0, buf1, 0, 4},
{1, buf2, 0, 4},
});
} else {
bg = utils::MakeBindGroup(device, bgl, {
{0, buf1, 0, 4},
});
}
return {std::move(pl), std::move(bg), std::move(buf1)};
}
// A test spec is a bunch of push constant types and expected values
enum PushConstantType {
Float,
Int,
UInt,
};
struct PushConstantSpecItem {
PushConstantType type;
int value;
};
using PushConstantSpec = std::vector<PushConstantSpecItem>;
PushConstantSpec MakeAllZeroSpec() const {
PushConstantSpec allZeros;
for (uint32_t i = 0; i < kMaxPushConstants; ++i) {
allZeros.push_back({Int, 0});
}
return allZeros;
}
// The GLSL code to define the push constant block for a given test spec
std::string MakePushConstantBlock(PushConstantSpec spec) {
std::string block = "layout(push_constant) uniform ConstantsBlock {\n";
for (size_t i = 0; i < spec.size(); ++i) {
block += " ";
switch (spec[i].type) {
case Float:
block += "float";
break;
case Int:
block += "int";
break;
case UInt:
block += "uint";
break;
}
block += " val" + std::to_string(i) + ";\n";
}
block += "} c;\n";
return block;
}
// The GLSL code to define the push constant test for a given test spec
std::string MakePushConstantTest(PushConstantSpec spec, std::string varName) {
std::string test = "bool " + varName + " = true;\n";
for (size_t i = 0; i < spec.size(); ++i) {
test += varName + " = " + varName + " && (c.val" + std::to_string(i) + " == ";
switch (spec[i].type) {
case Float:
test += "float";
break;
case Int:
test += "int";
break;
case UInt:
test += "uint";
break;
}
test += "(" + std::to_string(spec[i].value) + "));\n";
}
return test;
}
// The compute pipeline ANDs the result of the test in the SSBO
dawn::ComputePipeline MakeTestComputePipeline(const dawn::PipelineLayout& pl, PushConstantSpec spec) {
dawn::ShaderModule module = utils::CreateShaderModule(device, dawn::ShaderStage::Compute, (R"(
#version 450
layout(set = 0, binding = 0) buffer Result {
int success;
} result;
)" + MakePushConstantBlock(spec) + R"(
void main() {
)" + MakePushConstantTest(spec, "success") + R"(
if (success && result.success == 1) {
result.success = 1;
} else {
result.success = 0;
}
})").c_str()
);
dawn::ComputePipelineDescriptor descriptor;
descriptor.layout = pl;
dawn::PipelineStageDescriptor computeStage;
computeStage.module = module;
computeStage.entryPoint = "main";
descriptor.computeStage = &computeStage;
return device.CreateComputePipeline(&descriptor);
}
dawn::PipelineLayout MakeEmptyLayout() {
return utils::MakeBasicPipelineLayout(device, nullptr);
}
// The render pipeline adds one to the red channel for successful vertex push constant test
// and adds one to green for the frgament test.
dawn::RenderPipeline MakeTestRenderPipeline(dawn::PipelineLayout& layout, PushConstantSpec vsSpec, PushConstantSpec fsSpec) {
dawn::ShaderModule vsModule = utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, (R"(
#version 450
)" + MakePushConstantBlock(vsSpec) + R"(
layout(location = 0) out float red;
void main() {
red = 0.0f;
)" + MakePushConstantTest(vsSpec, "success") + R"(
if (success) {red = 1.0f / 255.0f;}
gl_Position = vec4(0.0f, 0.0f, 0.0f, 1.0f);
})").c_str()
);
dawn::ShaderModule fsModule = utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, (R"(
#version 450
)" + MakePushConstantBlock(fsSpec) + R"(
layout(location = 0) out vec4 color;
layout(location = 0) in float red;
void main() {
color = vec4(red, 0.0f, 0.0f, 0.0f);
)" + MakePushConstantTest(fsSpec, "success") + R"(
if (success) {color.g = 1.0f / 255.0f;}
})").c_str()
);
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = layout;
descriptor.cVertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
descriptor.primitiveTopology = dawn::PrimitiveTopology::PointList;
return device.CreateRenderPipeline(&descriptor);
}
};
// Test that push constants default to zero at the beginning of every compute passes.
TEST_P(PushConstantTest, ComputePassDefaultsToZero) {
auto binding = MakeTestBindings(false);
// Expect push constants to be zero in all dispatches of this test.
dawn::ComputePipeline pipeline = MakeTestComputePipeline(binding.layout, MakeAllZeroSpec());
uint32_t notZero = 42;
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::ComputePassEncoder pass = encoder.BeginComputePass();
// Test compute push constants are set to zero by default.
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, binding.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
// Set push constants to non-zero value to check they will be reset to zero
// on the next BeginComputePass
pass.SetPushConstants(dawn::ShaderStageBit::Compute, 0, 1, &notZero);
pass.EndPass();
}
{
dawn::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, binding.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, binding.resultBuffer, 0);
}
// Test that push constants default to zero at the beginning of render passes.
TEST_P(PushConstantTest, RenderPassDefaultsToZero) {
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
// Expect push constants to be zero in all draws of this test.
PushConstantSpec allZeros = MakeAllZeroSpec();
dawn::PipelineLayout layout = MakeEmptyLayout();
dawn::RenderPipeline pipeline = MakeTestRenderPipeline(layout, MakeAllZeroSpec(), MakeAllZeroSpec());
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
// Test render push constants are set to zero by default.
pass.SetPipeline(pipeline);
pass.Draw(1, 1, 0, 0);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 1, 0, 0), renderPass.color, 0, 0);
}
// Test setting push constants of various 32bit types.
TEST_P(PushConstantTest, VariousConstantTypes) {
struct {
int32_t v1;
uint32_t v2;
float v3;
} values = {-1, 3, 4.0f};
static_assert(sizeof(values) == 3 * sizeof(uint32_t), "");
auto binding = MakeTestBindings(false);
PushConstantSpec spec = {{Int, -1}, {UInt, 3}, {Float, 4}};
dawn::ComputePipeline pipeline = MakeTestComputePipeline(binding.layout, spec);
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPushConstants(dawn::ShaderStageBit::Compute, 0, 3, reinterpret_cast<uint32_t*>(&values));
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, binding.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, binding.resultBuffer, 0);
}
// Test that the push constants stay in between pipeline layout changes.
TEST_P(PushConstantTest, InheritThroughPipelineLayoutChange) {
// These bindings will have a different pipeline layout because binding 2 has an extra buffer.
auto binding1 = MakeTestBindings(false);
auto binding2 = MakeTestBindings(true);
PushConstantSpec spec1 = {{Int, 1}};
PushConstantSpec spec2 = {{Int, 2}};
dawn::ComputePipeline pipeline1 = MakeTestComputePipeline(binding1.layout, spec1);
dawn::ComputePipeline pipeline2 = MakeTestComputePipeline(binding2.layout, spec2);
uint32_t one = 1;
uint32_t two = 2;
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::ComputePassEncoder pass = encoder.BeginComputePass();
// Set Push constant before there is a pipeline set
pass.SetPushConstants(dawn::ShaderStageBit::Compute, 0, 1, &one);
pass.SetPipeline(pipeline1);
pass.SetBindGroup(0, binding1.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
// Change the push constant before changing pipeline layout
pass.SetPushConstants(dawn::ShaderStageBit::Compute, 0, 1, &two);
pass.SetPipeline(pipeline2);
pass.SetBindGroup(0, binding2.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, binding1.resultBuffer, 0);
EXPECT_BUFFER_U32_EQ(1, binding2.resultBuffer, 0);
}
// Try setting all push constants
TEST_P(PushConstantTest, SetAllConstantsToNonZero) {
PushConstantSpec spec;
std::array<uint32_t, kMaxPushConstants> values;
for (uint32_t i = 0; i < kMaxPushConstants; ++i) {
spec.push_back({Int, static_cast<int>(i + 1)});
values[i] = i + 1;
}
auto binding = MakeTestBindings(false);
dawn::ComputePipeline pipeline = MakeTestComputePipeline(binding.layout, spec);
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPushConstants(dawn::ShaderStageBit::Compute, 0, kMaxPushConstants, &values[0]);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, binding.bindGroup, 0, nullptr);
pass.Dispatch(1, 1, 1);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, binding.resultBuffer, 0);
}
// Try setting separate push constants for vertex and fragment stage
TEST_P(PushConstantTest, SeparateVertexAndFragmentConstants) {
PushConstantSpec vsSpec = {{Int, 1}};
PushConstantSpec fsSpec = {{Int, 2}};
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
dawn::PipelineLayout layout = MakeEmptyLayout();
dawn::RenderPipeline pipeline = MakeTestRenderPipeline(layout, vsSpec, fsSpec);
uint32_t one = 1;
uint32_t two = 2;
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPushConstants(dawn::ShaderStageBit::Vertex, 0, 1, &one);
pass.SetPushConstants(dawn::ShaderStageBit::Fragment, 0, 1, &two);
pass.SetPipeline(pipeline);
pass.Draw(1, 1, 0, 0);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 1, 0, 0), renderPass.color, 0, 0);
}
// Try setting push constants for vertex and fragment stage simulteanously
TEST_P(PushConstantTest, SimultaneousVertexAndFragmentConstants) {
PushConstantSpec spec = {{Int, 2}};
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
dawn::PipelineLayout layout = MakeEmptyLayout();
dawn::RenderPipeline pipeline = MakeTestRenderPipeline(layout, spec, spec);
uint32_t two = 2;
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
{
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPushConstants(dawn::ShaderStageBit::Vertex | dawn::ShaderStageBit::Fragment, 0, 1, &two);
pass.SetPipeline(pipeline);
pass.Draw(1, 1, 0, 0);
pass.EndPass();
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 1, 0, 0), renderPass.color, 0, 0);
}
DAWN_INSTANTIATE_TEST(PushConstantTest, MetalBackend, OpenGLBackend);