diff --git a/Android.mk b/Android.mk index 16b86e571..0d64a9adb 100644 --- a/Android.mk +++ b/Android.mk @@ -36,6 +36,7 @@ LOCAL_SRC_FILES:= \ src/pipeline_data.cc \ src/recipe.cc \ src/result.cc \ + src/sampler.cc \ src/script.cc \ src/shader.cc \ src/shader_compiler.cc \ @@ -49,6 +50,7 @@ LOCAL_SRC_FILES:= \ src/vkscript/parser.cc \ src/vkscript/section_parser.cc \ src/vulkan/buffer_descriptor.cc \ + src/vulkan/buffer_backed_descriptor.cc \ src/vulkan/command_buffer.cc \ src/vulkan/command_pool.cc \ src/vulkan/compute_pipeline.cc \ @@ -62,6 +64,7 @@ LOCAL_SRC_FILES:= \ src/vulkan/pipeline.cc \ src/vulkan/push_constant.cc \ src/vulkan/resource.cc \ + src/vulkan/sampler_descriptor.cc \ src/vulkan/transfer_buffer.cc \ src/vulkan/transfer_image.cc \ src/vulkan/vertex_buffer.cc \ diff --git a/docs/amber_script.md b/docs/amber_script.md index ca2ee56ec..5eeaa5b26 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -237,8 +237,8 @@ The following commands are all specified within the `PIPELINE` command. TODO(dsinclair): Sync the BufferTypes with the list of Vulkan Descriptor types. -A `pipeline` can have buffers bound. This includes buffers to contain image -attachment content, depth/stencil content, uniform buffers, etc. +A `pipeline` can have buffers or samplers bound. This includes buffers to +contain image attachment content, depth/stencil content, uniform buffers, etc. ```groovy # Attach |buffer_name| as an output color attachment at location |idx|. @@ -255,13 +255,21 @@ attachment content, depth/stencil content, uniform buffers, etc. # Attach |buffer_name| as the push_constant buffer. There can be only one # push constant buffer attached to a pipeline. - BIND BUFFER AS push_constant + BIND BUFFER {buffer_name} AS push_constant # Bind the buffer of the given |buffer_type| at the given descriptor set # and binding. The buffer will use a start index of 0. BIND BUFFER {buffer_name} AS {buffer_type} DESCRIPTOR_SET _id_ \ BINDING _id_ + # Attach |buffer_name| as a storage image. The provided buffer must + # be a `FORMAT` buffer. + BIND BUFFER {buffer_name} AS storage_image + + # Attach |buffer_name| as a sampled image. The provided buffer must + # be a `FORMAT` buffer. + BIND BUFFER {buffer_name} AS sampled_image + # Bind the sampler at the given descriptor set and binding. BIND SAMPLER {sampler_name} DESCRIPTOR_SET _id_ BINDING _id_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70ca4fae7..8b5dc7804 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,10 +27,11 @@ set(AMBER_SOURCES pipeline_data.cc recipe.cc result.cc - sleep.cc + sampler.cc script.cc shader.cc shader_compiler.cc + sleep.cc tokenizer.cc type.cc type_parser.cc @@ -127,6 +128,7 @@ if (${AMBER_ENABLE_TESTS}) amberscript/parser_pipeline_set_test.cc amberscript/parser_repeat_test.cc amberscript/parser_run_test.cc + amberscript/parser_sampler_test.cc amberscript/parser_set_test.cc amberscript/parser_shader_opt_test.cc amberscript/parser_shader_test.cc diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index 1be6fb67e..7e849c398 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -22,6 +22,7 @@ #include #include "src/make_unique.h" +#include "src/sampler.h" #include "src/shader_data.h" #include "src/tokenizer.h" #include "src/type_parser.h" @@ -122,6 +123,21 @@ std::unique_ptr ToType(const std::string& str) { return nullptr; } +AddressMode StrToAddressMode(std::string str) { + if (str == "repeat") + return AddressMode::kRepeat; + if (str == "mirrored_repeat") + return AddressMode::kMirroredRepeat; + if (str == "clamp_to_edge") + return AddressMode::kClampToEdge; + if (str == "clamp_to_border") + return AddressMode::kClampToBorder; + if (str == "mirror_clamp_to_edge") + return AddressMode::kMirrorClampToEdge; + + return AddressMode::kUnknown; +} + } // namespace Parser::Parser() : amber::Parser() {} @@ -166,6 +182,8 @@ Result Parser::Parse(const std::string& data) { r = ParseShaderBlock(); } else if (tok == "STRUCT") { r = ParseStruct(); + } else if (tok == "SAMPLER") { + r = ParseSampler(); } else { r = Result("unknown token: " + tok); } @@ -656,6 +674,8 @@ Result Parser::ToBufferType(const std::string& name, BufferType* type) { *type = BufferType::kStorage; else if (name == "storage_image") *type = BufferType::kStorageImage; + else if (name == "sampled_image") + *type = BufferType::kSampledImage; else return Result("unknown buffer_type: " + name); @@ -664,110 +684,146 @@ Result Parser::ToBufferType(const std::string& name, BufferType* type) { Result Parser::ParsePipelineBind(Pipeline* pipeline) { auto token = tokenizer_->NextToken(); - if (!token->IsString()) - return Result("missing BUFFER in BIND command"); - if (token->AsString() != "BUFFER") - return Result("missing BUFFER in BIND command"); - token = tokenizer_->NextToken(); if (!token->IsString()) - return Result("missing buffer name in BIND command"); + return Result("missing BUFFER or SAMPLER in BIND command"); - auto* buffer = script_->GetBuffer(token->AsString()); - if (!buffer) - return Result("unknown buffer: " + token->AsString()); + auto object_type = token->AsString(); - token = tokenizer_->NextToken(); - if (token->IsString() && token->AsString() == "AS") { + if (object_type == "BUFFER") { token = tokenizer_->NextToken(); if (!token->IsString()) - return Result("invalid token for BUFFER type"); + return Result("missing buffer name in BIND command"); - if (token->AsString() == "color") { - token = tokenizer_->NextToken(); - if (!token->IsString() || token->AsString() != "LOCATION") - return Result("BIND missing LOCATION"); + auto* buffer = script_->GetBuffer(token->AsString()); + if (!buffer) + return Result("unknown buffer: " + token->AsString()); + token = tokenizer_->NextToken(); + if (token->IsString() && token->AsString() == "AS") { token = tokenizer_->NextToken(); - if (!token->IsInteger()) - return Result("invalid value for BIND LOCATION"); + if (!token->IsString()) + return Result("invalid token for BUFFER type"); - buffer->SetBufferType(BufferType::kColor); + if (token->AsString() == "color") { + token = tokenizer_->NextToken(); + if (!token->IsString() || token->AsString() != "LOCATION") + return Result("BIND missing LOCATION"); - Result r = pipeline->AddColorAttachment(buffer, token->AsUint32()); - if (!r.IsSuccess()) - return r; - } else if (token->AsString() == "depth_stencil") { - buffer->SetBufferType(BufferType::kDepth); - Result r = pipeline->SetDepthBuffer(buffer); - if (!r.IsSuccess()) - return r; - } else if (token->AsString() == "push_constant") { - buffer->SetBufferType(BufferType::kPushConstant); - Result r = pipeline->SetPushConstantBuffer(buffer); - if (!r.IsSuccess()) - return r; - } else if (token->AsString() == "storage_image") { - buffer->SetBufferType(BufferType::kStorageImage); - } else { - BufferType type = BufferType::kColor; - Result r = ToBufferType(token->AsString(), &type); - if (!r.IsSuccess()) - return r; + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for BIND LOCATION"); - if (buffer->GetBufferType() == BufferType::kUnknown) - buffer->SetBufferType(type); - else if (buffer->GetBufferType() != type) - return Result("buffer type does not match intended usage"); - } - } + buffer->SetBufferType(BufferType::kColor); - if (buffer->GetBufferType() == BufferType::kUnknown || - buffer->GetBufferType() == BufferType::kStorage || - buffer->GetBufferType() == BufferType::kUniform || - buffer->GetBufferType() == BufferType::kStorageImage) { - // If AS was parsed above consume the next token. - if (buffer->GetBufferType() != BufferType::kUnknown) - token = tokenizer_->NextToken(); - // DESCRIPTOR_SET requires a buffer type to have been specified. - if (buffer->GetBufferType() != BufferType::kUnknown && token->IsString() && - token->AsString() == "DESCRIPTOR_SET") { - token = tokenizer_->NextToken(); - if (!token->IsInteger()) - return Result("invalid value for DESCRIPTOR_SET in BIND command"); - uint32_t descriptor_set = token->AsUint32(); + Result r = pipeline->AddColorAttachment(buffer, token->AsUint32()); + if (!r.IsSuccess()) + return r; + } else if (token->AsString() == "depth_stencil") { + buffer->SetBufferType(BufferType::kDepth); + Result r = pipeline->SetDepthBuffer(buffer); + if (!r.IsSuccess()) + return r; + } else if (token->AsString() == "push_constant") { + buffer->SetBufferType(BufferType::kPushConstant); + Result r = pipeline->SetPushConstantBuffer(buffer); + if (!r.IsSuccess()) + return r; + } else if (token->AsString() == "storage_image") { + buffer->SetBufferType(BufferType::kStorageImage); + } else if (token->AsString() == "sampled_image") { + buffer->SetBufferType(BufferType::kSampledImage); + } else { + BufferType type = BufferType::kColor; + Result r = ToBufferType(token->AsString(), &type); + if (!r.IsSuccess()) + return r; - token = tokenizer_->NextToken(); - if (!token->IsString() || token->AsString() != "BINDING") - return Result("missing BINDING for BIND command"); + if (buffer->GetBufferType() == BufferType::kUnknown) + buffer->SetBufferType(type); + else if (buffer->GetBufferType() != type) + return Result("buffer type does not match intended usage"); + } + } - token = tokenizer_->NextToken(); - if (!token->IsInteger()) - return Result("invalid value for BINDING in BIND command"); - pipeline->AddBuffer(buffer, descriptor_set, token->AsUint32()); - } else if (token->IsString() && token->AsString() == "KERNEL") { - token = tokenizer_->NextToken(); - if (!token->IsString()) - return Result("missing kernel arg identifier"); + if (buffer->GetBufferType() == BufferType::kUnknown || + buffer->GetBufferType() == BufferType::kStorage || + buffer->GetBufferType() == BufferType::kUniform || + buffer->GetBufferType() == BufferType::kStorageImage || + buffer->GetBufferType() == BufferType::kSampledImage) { + // If AS was parsed above consume the next token. + if (buffer->GetBufferType() != BufferType::kUnknown) + token = tokenizer_->NextToken(); + // DESCRIPTOR_SET requires a buffer type to have been specified. + if (buffer->GetBufferType() != BufferType::kUnknown && + token->IsString() && token->AsString() == "DESCRIPTOR_SET") { + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for DESCRIPTOR_SET in BIND command"); + uint32_t descriptor_set = token->AsUint32(); - if (token->AsString() == "ARG_NAME") { token = tokenizer_->NextToken(); - if (!token->IsString()) - return Result("expected argument identifier"); + if (!token->IsString() || token->AsString() != "BINDING") + return Result("missing BINDING for BIND command"); - pipeline->AddBuffer(buffer, token->AsString()); - } else if (token->AsString() == "ARG_NUMBER") { token = tokenizer_->NextToken(); if (!token->IsInteger()) - return Result("expected argument number"); - - pipeline->AddBuffer(buffer, token->AsUint32()); + return Result("invalid value for BINDING in BIND command"); + pipeline->AddBuffer(buffer, descriptor_set, token->AsUint32()); + } else if (token->IsString() && token->AsString() == "KERNEL") { + token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("missing kernel arg identifier"); + + if (token->AsString() == "ARG_NAME") { + token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("expected argument identifier"); + + pipeline->AddBuffer(buffer, token->AsString()); + } else if (token->AsString() == "ARG_NUMBER") { + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("expected argument number"); + + pipeline->AddBuffer(buffer, token->AsUint32()); + } else { + return Result("missing ARG_NAME or ARG_NUMBER keyword"); + } } else { - return Result("missing ARG_NAME or ARG_NUMBER keyword"); + return Result("missing DESCRIPTOR_SET or KERNEL for BIND command"); } - } else { - return Result("missing DESCRIPTOR_SET or KERNEL for BIND command"); } + } else if (object_type == "SAMPLER") { + token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("missing sampler name in BIND command"); + + auto* sampler = script_->GetSampler(token->AsString()); + if (!sampler) + return Result("unknown sampler: " + token->AsString()); + + token = tokenizer_->NextToken(); + + if (!token->IsString() || token->AsString() != "DESCRIPTOR_SET") + return Result("missing DESCRIPTOR_SET for BIND command"); + + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for DESCRIPTOR_SET in BIND command"); + uint32_t descriptor_set = token->AsUint32(); + + token = tokenizer_->NextToken(); + if (!token->IsString() || token->AsString() != "BINDING") + return Result("missing BINDING for BIND command"); + + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for BINDING in BIND command"); + pipeline->AddSampler(sampler, descriptor_set, token->AsUint32()); + + } else { + return Result("missing BUFFER or SAMPLER in BIND command"); } return ValidateEndOfStatement("BIND command"); @@ -1717,8 +1773,51 @@ Result Parser::ParseExpect() { probe->SetA(token->AsFloat() / 255.f); } + token = tokenizer_->NextToken(); + if (token->IsString() && token->AsString() == "TOLERANCE") { + std::vector tolerances; + + token = tokenizer_->NextToken(); + while (!token->IsEOL() && !token->IsEOS()) { + if (!token->IsInteger() && !token->IsDouble()) + break; + + Result r = token->ConvertToDouble(); + if (!r.IsSuccess()) + return r; + + double value = token->AsDouble(); + token = tokenizer_->NextToken(); + if (token->IsString() && token->AsString() == "%") { + tolerances.push_back(Probe::Tolerance{true, value}); + token = tokenizer_->NextToken(); + } else { + tolerances.push_back(Probe::Tolerance{false, value}); + } + } + if (tolerances.empty()) + return Result("TOLERANCE specified but no tolerances provided"); + + if (!probe->IsRGBA() && tolerances.size() > 3) { + return Result( + "TOLERANCE for an RGB comparison has a maximum of 3 values"); + } + + if (tolerances.size() > 4) { + return Result( + "TOLERANCE for an RGBA comparison has a maximum of 4 values"); + } + + probe->SetTolerances(std::move(tolerances)); + } + + if (!token->IsEOL() && !token->IsEOS()) { + return Result("extra parameters after EXPECT command"); + } + command_list_.push_back(std::move(probe)); - return ValidateEndOfStatement("EXPECT command"); + + return {}; } auto probe = MakeUnique(buffer); @@ -2052,5 +2151,105 @@ Result Parser::ParseSet() { return ValidateEndOfStatement("SET command"); } +Result Parser::ParseSampler() { + auto token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("invalid token when looking for sampler name"); + + auto sampler = MakeUnique(); + sampler->SetName(token->AsString()); + + token = tokenizer_->NextToken(); + while (!token->IsEOS() && !token->IsEOL()) { + if (!token->IsString()) + return Result("invalid token when looking for sampler parameters"); + + auto param = token->AsString(); + if (param == "MAG_FILTER") { + token = tokenizer_->NextToken(); + + if (!token->IsString()) + return Result("invalid token when looking for MAG_FILTER value"); + + auto filter = token->AsString(); + + if (filter == "linear") + sampler->SetMagFilter(FilterType::kLinear); + else if (filter == "nearest") + sampler->SetMagFilter(FilterType::kNearest); + else + return Result("invalid MAG_FILTER value " + filter); + } else if (param == "MIN_FILTER") { + token = tokenizer_->NextToken(); + + if (!token->IsString()) + return Result("invalid token when looking for MIN_FILTER value"); + + auto filter = token->AsString(); + + if (filter == "linear") + sampler->SetMinFilter(FilterType::kLinear); + else if (filter == "nearest") + sampler->SetMinFilter(FilterType::kNearest); + else + return Result("invalid MIN_FILTER value " + filter); + } else if (param == "ADDRESS_MODE_U") { + token = tokenizer_->NextToken(); + + if (!token->IsString()) + return Result("invalid token when looking for ADDRESS_MODE_U value"); + + auto mode_str = token->AsString(); + auto mode = StrToAddressMode(mode_str); + + if (mode == AddressMode::kUnknown) + return Result("invalid ADDRESS_MODE_U value " + mode_str); + + sampler->SetAddressModeU(mode); + } else if (param == "ADDRESS_MODE_V") { + token = tokenizer_->NextToken(); + + if (!token->IsString()) + return Result("invalid token when looking for ADDRESS_MODE_V value"); + + auto mode_str = token->AsString(); + auto mode = StrToAddressMode(mode_str); + + if (mode == AddressMode::kUnknown) + return Result("invalid ADDRESS_MODE_V value " + mode_str); + + sampler->SetAddressModeV(mode); + } else if (param == "BORDER_COLOR") { + token = tokenizer_->NextToken(); + + if (!token->IsString()) + return Result("invalid token when looking for BORDER_COLOR value"); + + auto color_str = token->AsString(); + + if (color_str == "float_transparent_black") + sampler->SetBorderColor(BorderColor::kFloatTransparentBlack); + else if (color_str == "int_transparent_black") + sampler->SetBorderColor(BorderColor::kIntTransparentBlack); + else if (color_str == "float_opaque_black") + sampler->SetBorderColor(BorderColor::kFloatOpaqueBlack); + else if (color_str == "int_opaque_black") + sampler->SetBorderColor(BorderColor::kIntOpaqueBlack); + else if (color_str == "float_opaque_white") + sampler->SetBorderColor(BorderColor::kFloatOpaqueWhite); + else if (color_str == "int_opaque_white") + sampler->SetBorderColor(BorderColor::kIntOpaqueWhite); + else + return Result("invalid BORDER_COLOR value " + color_str); + } else { + return Result("unexpected sampler parameter " + param); + } + + token = tokenizer_->NextToken(); + } + + return script_->AddSampler(std::move(sampler)); +} + } // namespace amberscript } // namespace amber diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 2412208e3..88cfe9fc2 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -27,6 +27,7 @@ namespace amber { class Tokenizer; +class Token; namespace amberscript { @@ -80,6 +81,7 @@ class Parser : public amber::Parser { Result ParsePipelineBody(const std::string& cmd_name, std::unique_ptr pipeline); Result ParseShaderSpecialization(Pipeline* pipeline); + Result ParseSampler(); /// Parses a set of values out of the token stream. |name| is the name of the /// current command we're parsing for error purposes. The |type| is the type diff --git a/src/amberscript/parser_bind_test.cc b/src/amberscript/parser_bind_test.cc index 556bf6317..57c13f2bd 100644 --- a/src/amberscript/parser_bind_test.cc +++ b/src/amberscript/parser_bind_test.cc @@ -1459,5 +1459,234 @@ END)"; EXPECT_EQ("11: missing BINDING for BIND command", r.Error()); } +TEST_F(AmberScriptParserTest, BindBufferSampledImage) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +BUFFER texture FORMAT R8G8B8A8_UNORM +BUFFER framebuffer FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND BUFFER framebuffer AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& bufs = pipeline->GetBuffers(); + ASSERT_EQ(1U, bufs.size()); + EXPECT_EQ(BufferType::kSampledImage, bufs[0].buffer->GetBufferType()); +} + +TEST_F(AmberScriptParserTest, BindBufferSampledImageMissingDescriptorSetValue) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +BUFFER texture FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET BINDING 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: invalid value for DESCRIPTOR_SET in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferSampledImageMissingDescriptorSet) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +BUFFER texture FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER texture AS sampled_image BINDING 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: missing DESCRIPTOR_SET or KERNEL for BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferSampledImageMissingBindingValue) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +BUFFER texture FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: invalid value for BINDING in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferSampledImageMissingBinding) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +BUFFER texture FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: missing BINDING for BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindSampler) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +SAMPLER sampler +BUFFER framebuffer FORMAT R8G8B8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING 0 + BIND BUFFER framebuffer AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& samplers = pipeline->GetSamplers(); + ASSERT_EQ(1U, samplers.size()); +} + +TEST_F(AmberScriptParserTest, BindSamplerMissingDescriptorSetValue) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +SAMPLER sampler + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND SAMPLER sampler DESCRIPTOR_SET BINDING 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: invalid value for DESCRIPTOR_SET in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindSamplerMissingDescriptorSet) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +SAMPLER sampler + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND SAMPLER sampler BINDING 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: missing DESCRIPTOR_SET for BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindSamplerMissingBindingValue) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +SAMPLER sampler + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: invalid value for BINDING in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindSamplerMissingBinding) { + std::string in = R"( +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +# GLSL Shader +END + +SAMPLER sampler + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND SAMPLER sampler DESCRIPTOR_SET 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: missing BINDING for BIND command", r.Error()); +} + } // namespace amberscript } // namespace amber diff --git a/src/amberscript/parser_expect_test.cc b/src/amberscript/parser_expect_test.cc index dc81ca867..590b8f72f 100644 --- a/src/amberscript/parser_expect_test.cc +++ b/src/amberscript/parser_expect_test.cc @@ -986,6 +986,175 @@ EXPECT orig_buf IDX 5 TOLERANCE 1 2 3 4 NE 11)"; EXPECT_EQ("3: TOLERANCE only available with EQ probes", r.Error()); } +TEST_F(AmberScriptParserTest, ExpectEqRgbaToleranceOneValue) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8A8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGBA 128 0 128 255 TOLERANCE 3)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& commands = script->GetCommands(); + ASSERT_EQ(1U, commands.size()); + + auto* cmd = commands[0].get(); + ASSERT_TRUE(cmd->IsProbe()); + + auto* probe = cmd->AsProbe(); + EXPECT_TRUE(probe->IsRGBA()); + EXPECT_TRUE(probe->HasTolerances()); + + auto& tolerances = probe->GetTolerances(); + ASSERT_EQ(1U, tolerances.size()); + + EXPECT_FALSE(tolerances[0].is_percent); + EXPECT_FLOAT_EQ(3.f, static_cast(tolerances[0].value)); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbaToleranceMultiValue) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8A8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGBA 128 0 128 255 TOLERANCE 5.2 2% 4 1.5)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& commands = script->GetCommands(); + ASSERT_EQ(1U, commands.size()); + + auto* cmd = commands[0].get(); + ASSERT_TRUE(cmd->IsProbe()); + + auto* probe = cmd->AsProbe(); + EXPECT_TRUE(probe->IsRGBA()); + EXPECT_TRUE(probe->HasTolerances()); + + auto& tolerances = probe->GetTolerances(); + ASSERT_EQ(4U, tolerances.size()); + + EXPECT_FALSE(tolerances[0].is_percent); + EXPECT_FLOAT_EQ(5.2f, static_cast(tolerances[0].value)); + + EXPECT_TRUE(tolerances[1].is_percent); + EXPECT_FLOAT_EQ(2.0f, static_cast(tolerances[1].value)); + + EXPECT_FALSE(tolerances[2].is_percent); + EXPECT_FLOAT_EQ(4.0f, static_cast(tolerances[2].value)); + + EXPECT_FALSE(tolerances[3].is_percent); + EXPECT_FLOAT_EQ(1.5f, static_cast(tolerances[3].value)); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbaToleranceTooManyValues) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8A8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGBA 128 0 128 255 TOLERANCE 5.2 2% 4 1.5 6)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: TOLERANCE for an RGBA comparison has a maximum of 4 values", + r.Error()); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbaToleranceExtraParameters) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8A8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGBA 128 0 128 255 TOLERANCE 3 FOO)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: extra parameters after EXPECT command", r.Error()); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbToleranceOneValue) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGB 128 0 128 TOLERANCE 3)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& commands = script->GetCommands(); + ASSERT_EQ(1U, commands.size()); + + auto* cmd = commands[0].get(); + ASSERT_TRUE(cmd->IsProbe()); + + auto* probe = cmd->AsProbe(); + EXPECT_FALSE(probe->IsRGBA()); + EXPECT_TRUE(probe->HasTolerances()); + + auto& tolerances = probe->GetTolerances(); + ASSERT_EQ(1U, tolerances.size()); + + EXPECT_FALSE(tolerances[0].is_percent); + EXPECT_FLOAT_EQ(3.f, static_cast(tolerances[0].value)); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbToleranceMultiValue) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGB 128 0 128 TOLERANCE 5.2 2% 4)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& commands = script->GetCommands(); + ASSERT_EQ(1U, commands.size()); + + auto* cmd = commands[0].get(); + ASSERT_TRUE(cmd->IsProbe()); + + auto* probe = cmd->AsProbe(); + EXPECT_FALSE(probe->IsRGBA()); + EXPECT_TRUE(probe->HasTolerances()); + + auto& tolerances = probe->GetTolerances(); + ASSERT_EQ(3U, tolerances.size()); + + EXPECT_FALSE(tolerances[0].is_percent); + EXPECT_FLOAT_EQ(5.2f, static_cast(tolerances[0].value)); + + EXPECT_TRUE(tolerances[1].is_percent); + EXPECT_FLOAT_EQ(2.0f, static_cast(tolerances[1].value)); + + EXPECT_FALSE(tolerances[2].is_percent); + EXPECT_FLOAT_EQ(4.0f, static_cast(tolerances[2].value)); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbToleranceTooManyValues) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGB 128 0 128 TOLERANCE 5.2 2% 4 1.5)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: TOLERANCE for an RGB comparison has a maximum of 3 values", + r.Error()); +} + +TEST_F(AmberScriptParserTest, ExpectEqRgbToleranceExtraParameters) { + std::string in = R"( +BUFFER buf FORMAT R8G8B8_UNORM +EXPECT buf IDX 80 80 SIZE 5 8 EQ_RGB 128 0 128 TOLERANCE 3 FOO)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: extra parameters after EXPECT command", r.Error()); +} + TEST_F(AmberScriptParserTest, ExpectRMSEBuffer) { std::string in = R"( BUFFER buf_1 DATA_TYPE int32 SIZE 10 FILL 11 diff --git a/src/amberscript/parser_sampler_test.cc b/src/amberscript/parser_sampler_test.cc new file mode 100644 index 000000000..7c8bf07ba --- /dev/null +++ b/src/amberscript/parser_sampler_test.cc @@ -0,0 +1,132 @@ +// Copyright 2019 The Amber 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 parseried. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" +#include "src/amberscript/parser.h" + +namespace amber { +namespace amberscript { + +using AmberScriptParserTest = testing::Test; + +TEST_F(AmberScriptParserTest, SamplerDefaultValues) { + std::string in = "SAMPLER sampler"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& samplers = script->GetSamplers(); + ASSERT_EQ(1U, samplers.size()); + + ASSERT_TRUE(samplers[0] != nullptr); + EXPECT_EQ("sampler", samplers[0]->GetName()); + + auto* sampler = samplers[0].get(); + EXPECT_EQ(FilterType::kNearest, sampler->GetMagFilter()); + EXPECT_EQ(FilterType::kNearest, sampler->GetMinFilter()); + EXPECT_EQ(FilterType::kNearest, sampler->GetMipmapMode()); + EXPECT_EQ(AddressMode::kRepeat, sampler->GetAddressModeU()); + EXPECT_EQ(AddressMode::kRepeat, sampler->GetAddressModeV()); + EXPECT_EQ(BorderColor::kFloatTransparentBlack, sampler->GetBorderColor()); +} + +TEST_F(AmberScriptParserTest, SamplerCustomValues) { + std::string in = R"( +SAMPLER sampler MAG_FILTER linear \ + MIN_FILTER linear \ + ADDRESS_MODE_U clamp_to_edge \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR float_opaque_white)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& samplers = script->GetSamplers(); + ASSERT_EQ(1U, samplers.size()); + + ASSERT_TRUE(samplers[0] != nullptr); + EXPECT_EQ("sampler", samplers[0]->GetName()); + + auto* sampler = samplers[0].get(); + EXPECT_EQ(FilterType::kLinear, sampler->GetMagFilter()); + EXPECT_EQ(FilterType::kLinear, sampler->GetMinFilter()); + EXPECT_EQ(FilterType::kNearest, sampler->GetMipmapMode()); + EXPECT_EQ(AddressMode::kClampToEdge, sampler->GetAddressModeU()); + EXPECT_EQ(AddressMode::kClampToBorder, sampler->GetAddressModeV()); + EXPECT_EQ(BorderColor::kFloatOpaqueWhite, sampler->GetBorderColor()); +} + +TEST_F(AmberScriptParserTest, SamplerUnexpectedParameter) { + std::string in = R"( +SAMPLER sampler MAG_FILTER linear \ + FOO \ + ADDRESS_MODE_U clamp_to_edge)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: unexpected sampler parameter FOO", r.Error()); +} + +TEST_F(AmberScriptParserTest, SamplerInvalidMagFilter) { + std::string in = "SAMPLER sampler MAG_FILTER foo"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid MAG_FILTER value foo", r.Error()); +} + +TEST_F(AmberScriptParserTest, SamplerInvalidMinFilter) { + std::string in = "SAMPLER sampler MIN_FILTER foo"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid MIN_FILTER value foo", r.Error()); +} + +TEST_F(AmberScriptParserTest, SamplerInvalidAddressModeU) { + std::string in = "SAMPLER sampler ADDRESS_MODE_U foo"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid ADDRESS_MODE_U value foo", r.Error()); +} + +TEST_F(AmberScriptParserTest, SamplerInvalidAddressModeV) { + std::string in = "SAMPLER sampler ADDRESS_MODE_V foo"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid ADDRESS_MODE_V value foo", r.Error()); +} + +TEST_F(AmberScriptParserTest, SamplerInvalidBorderColor) { + std::string in = "SAMPLER sampler BORDER_COLOR foo"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid BORDER_COLOR value foo", r.Error()); +} + +} // namespace amberscript +} // namespace amber diff --git a/src/command.cc b/src/command.cc index 84db7febc..00a82bb58 100644 --- a/src/command.cc +++ b/src/command.cc @@ -120,11 +120,21 @@ ProbeSSBOCommand::ProbeSSBOCommand(Buffer* buffer) ProbeSSBOCommand::~ProbeSSBOCommand() = default; +BindableResourceCommand::BindableResourceCommand(Type type, Pipeline* pipeline) + : PipelineCommand(type, pipeline) {} + +BindableResourceCommand::~BindableResourceCommand() = default; + BufferCommand::BufferCommand(BufferType type, Pipeline* pipeline) - : PipelineCommand(Type::kBuffer, pipeline), buffer_type_(type) {} + : BindableResourceCommand(Type::kBuffer, pipeline), buffer_type_(type) {} BufferCommand::~BufferCommand() = default; +SamplerCommand::SamplerCommand(Pipeline* pipeline) + : BindableResourceCommand(Type::kSampler, pipeline) {} + +SamplerCommand::~SamplerCommand() = default; + CopyCommand::CopyCommand(Buffer* buffer_from, Buffer* buffer_to) : Command(Type::kCopy), buffer_from_(buffer_from), buffer_to_(buffer_to) {} diff --git a/src/command.h b/src/command.h index 66c5ca498..ee93410d3 100644 --- a/src/command.h +++ b/src/command.h @@ -26,6 +26,7 @@ #include "src/buffer.h" #include "src/command_data.h" #include "src/pipeline_data.h" +#include "src/sampler.h" namespace amber { @@ -65,7 +66,8 @@ class Command { kProbe, kProbeSSBO, kBuffer, - kRepeat + kRepeat, + kSampler }; virtual ~Command(); @@ -413,12 +415,35 @@ class ProbeSSBOCommand : public Probe { std::vector values_; }; +/// Base class for BufferCommand and SamplerCommand to handle binding. +class BindableResourceCommand : public PipelineCommand { + public: + BindableResourceCommand(Type type, Pipeline* pipeline); + virtual ~BindableResourceCommand(); + + void SetDescriptorSet(uint32_t set) { descriptor_set_ = set; } + uint32_t GetDescriptorSet() const { return descriptor_set_; } + + void SetBinding(uint32_t num) { binding_num_ = num; } + uint32_t GetBinding() const { return binding_num_; } + + private: + uint32_t descriptor_set_ = 0; + uint32_t binding_num_ = 0; +}; + /// Command to set the size of a buffer, or update a buffers contents. -class BufferCommand : public PipelineCommand { +class BufferCommand : public BindableResourceCommand { public: - enum class BufferType { kSSBO, kUniform, kPushConstant, kStorageImage }; + enum class BufferType { + kSSBO, + kUniform, + kPushConstant, + kStorageImage, + kSampledImage + }; - explicit BufferCommand(BufferType type, Pipeline* pipeline); + BufferCommand(BufferType type, Pipeline* pipeline); ~BufferCommand() override; bool IsSSBO() const { return buffer_type_ == BufferType::kSSBO; } @@ -426,6 +451,9 @@ class BufferCommand : public PipelineCommand { bool IsStorageImage() const { return buffer_type_ == BufferType::kStorageImage; } + bool IsSampledImage() const { + return buffer_type_ == BufferType::kSampledImage; + } bool IsPushConstant() const { return buffer_type_ == BufferType::kPushConstant; } @@ -433,12 +461,6 @@ class BufferCommand : public PipelineCommand { void SetIsSubdata() { is_subdata_ = true; } bool IsSubdata() const { return is_subdata_; } - void SetDescriptorSet(uint32_t set) { descriptor_set_ = set; } - uint32_t GetDescriptorSet() const { return descriptor_set_; } - - void SetBinding(uint32_t num) { binding_num_ = num; } - uint32_t GetBinding() const { return binding_num_; } - void SetOffset(uint32_t offset) { offset_ = offset; } uint32_t GetOffset() const { return offset_; } @@ -454,12 +476,25 @@ class BufferCommand : public PipelineCommand { Buffer* buffer_ = nullptr; BufferType buffer_type_; bool is_subdata_ = false; - uint32_t descriptor_set_ = 0; - uint32_t binding_num_ = 0; uint32_t offset_ = 0; std::vector values_; }; +/// Command for setting sampler parameters and binding. +class SamplerCommand : public BindableResourceCommand { + public: + explicit SamplerCommand(Pipeline* pipeline); + ~SamplerCommand() override; + + void SetSampler(Sampler* sampler) { sampler_ = sampler; } + Sampler* GetSampler() const { return sampler_; } + + std::string ToString() const override { return "SamplerCommand"; } + + private: + Sampler* sampler_ = nullptr; +}; + /// Command to clear the colour attachments. class ClearCommand : public PipelineCommand { public: diff --git a/src/pipeline.cc b/src/pipeline.cc index 8f23ca0c9..46ddea403 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -363,6 +363,7 @@ void Pipeline::AddBuffer(Buffer* buf, auto& info = buffers_.back(); info.descriptor_set = descriptor_set; info.binding = binding; + info.type = buf->GetBufferType(); } void Pipeline::AddBuffer(Buffer* buf, const std::string& arg_name) { @@ -400,6 +401,24 @@ void Pipeline::AddBuffer(Buffer* buf, uint32_t arg_no) { info.binding = std::numeric_limits::max(); } +void Pipeline::AddSampler(Sampler* sampler, + uint32_t descriptor_set, + uint32_t binding) { + // If this sampler binding already exists, overwrite with the new sampler. + for (auto& info : samplers_) { + if (info.descriptor_set == descriptor_set && info.binding == binding) { + info.sampler = sampler; + return; + } + } + + samplers_.push_back(SamplerInfo{sampler}); + + auto& info = samplers_.back(); + info.descriptor_set = descriptor_set; + info.binding = binding; +} + Result Pipeline::UpdateOpenCLBufferBindings() { if (!IsCompute() || GetShaders().empty() || GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC) diff --git a/src/pipeline.h b/src/pipeline.h index e874613ab..8793f05e0 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -24,6 +24,7 @@ #include "amber/result.h" #include "src/buffer.h" +#include "src/sampler.h" #include "src/shader.h" namespace amber { @@ -133,6 +134,17 @@ class Pipeline { uint32_t location = 0; std::string arg_name = ""; uint32_t arg_no = 0; + BufferType type = BufferType::kUnknown; + }; + + /// Information on a sampler attached to the pipeline. + struct SamplerInfo { + SamplerInfo() = default; + explicit SamplerInfo(Sampler* samp) : sampler(samp) {} + + Sampler* sampler = nullptr; + uint32_t descriptor_set = 0; + uint32_t binding = 0; }; static const char* kGeneratedColorBuffer; @@ -220,6 +232,12 @@ class Pipeline { /// Returns information on all buffers in this pipeline. const std::vector& GetBuffers() const { return buffers_; } + /// Adds |sampler| to the pipeline at the given |descriptor_set| and + /// |binding|. + void AddSampler(Sampler* sampler, uint32_t descriptor_set, uint32_t binding); + /// Returns information on all samplers in this pipeline. + const std::vector& GetSamplers() const { return samplers_; } + /// Updates the descriptor set and binding info for the OpenCL-C kernel bound /// to the pipeline. No effect for other shader formats. Result UpdateOpenCLBufferBindings(); @@ -272,6 +290,7 @@ class Pipeline { std::vector vertex_buffers_; std::vector buffers_; std::vector> types_; + std::vector samplers_; std::vector> formats_; BufferInfo depth_buffer_; BufferInfo push_constant_buffer_; diff --git a/src/sampler.cc b/src/sampler.cc new file mode 100644 index 000000000..b3fe47bbf --- /dev/null +++ b/src/sampler.cc @@ -0,0 +1,22 @@ +// Copyright 2019 The Amber 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 "src/sampler.h" + +namespace amber { + +Sampler::Sampler() = default; +Sampler::~Sampler() = default; + +} // namespace amber diff --git a/src/sampler.h b/src/sampler.h new file mode 100644 index 000000000..376720e1d --- /dev/null +++ b/src/sampler.h @@ -0,0 +1,89 @@ +// Copyright 2019 The Amber 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. + +#ifndef SRC_SAMPLER_H_ +#define SRC_SAMPLER_H_ + +#include +#include +#include +#include +#include + +#include "amber/result.h" +#include "amber/value.h" +#include "src/format.h" + +namespace amber { + +enum class FilterType : int8_t { kUnknown = -1, kNearest = 0, kLinear = 1 }; + +enum class AddressMode : int8_t { + kUnknown = -1, + kRepeat = 0, + kMirroredRepeat = 1, + kClampToEdge = 2, + kClampToBorder = 3, + kMirrorClampToEdge = 4 +}; + +enum class BorderColor : int8_t { + kUnknown = -1, + kFloatTransparentBlack = 0, + kIntTransparentBlack = 1, + kFloatOpaqueBlack = 2, + kIntOpaqueBlack = 3, + kFloatOpaqueWhite = 4, + kIntOpaqueWhite = 5 +}; + +class Sampler { + public: + Sampler(); + ~Sampler(); + + void SetName(const std::string& name) { name_ = name; } + std::string GetName() const { return name_; } + + void SetMagFilter(FilterType filter) { mag_filter_ = filter; } + FilterType GetMagFilter() const { return mag_filter_; } + + void SetMinFilter(FilterType filter) { min_filter_ = filter; } + FilterType GetMinFilter() const { return min_filter_; } + + void SetMipmapMode(FilterType filter) { mipmap_mode_ = filter; } + FilterType GetMipmapMode() const { return mipmap_mode_; } + + void SetAddressModeU(AddressMode mode) { address_mode_u_ = mode; } + AddressMode GetAddressModeU() const { return address_mode_u_; } + + void SetAddressModeV(AddressMode mode) { address_mode_v_ = mode; } + AddressMode GetAddressModeV() const { return address_mode_v_; } + + void SetBorderColor(BorderColor color) { border_color_ = color; } + BorderColor GetBorderColor() const { return border_color_; } + + private: + std::string name_; + FilterType min_filter_ = FilterType::kNearest; + FilterType mag_filter_ = FilterType::kNearest; + FilterType mipmap_mode_ = FilterType::kNearest; + AddressMode address_mode_u_ = AddressMode::kRepeat; + AddressMode address_mode_v_ = AddressMode::kRepeat; + BorderColor border_color_ = BorderColor::kFloatTransparentBlack; +}; + +} // namespace amber + +#endif // SRC_SAMPLER_H_ diff --git a/src/script.h b/src/script.h index cb28873c9..b8f881b20 100644 --- a/src/script.h +++ b/src/script.h @@ -29,6 +29,7 @@ #include "src/engine.h" #include "src/format.h" #include "src/pipeline.h" +#include "src/sampler.h" #include "src/shader.h" namespace amber { @@ -130,6 +131,28 @@ class Script : public RecipeImpl { return buffers_; } + /// Adds |sampler| to the list of known sampler. The |sampler| must have a + /// unique name over all samplers in the script. + Result AddSampler(std::unique_ptr sampler) { + if (name_to_sampler_.count(sampler->GetName()) > 0) + return Result("duplicate sampler name provided"); + + samplers_.push_back(std::move(sampler)); + name_to_sampler_[samplers_.back()->GetName()] = samplers_.back().get(); + return {}; + } + + /// Retrieves the sampler with |name|, |nullptr| if not found. + Sampler* GetSampler(const std::string& name) const { + auto it = name_to_sampler_.find(name); + return it == name_to_sampler_.end() ? nullptr : it->second; + } + + /// Retrieves a list of all samplers. + const std::vector>& GetSamplers() const { + return samplers_; + } + /// Adds |feature| to the list of features that must be supported by the /// engine. void AddRequiredFeature(const std::string& feature) { @@ -212,11 +235,13 @@ class Script : public RecipeImpl { std::string spv_env_; std::map name_to_shader_; std::map name_to_buffer_; + std::map name_to_sampler_; std::map name_to_pipeline_; std::map> name_to_type_; std::vector> shaders_; std::vector> commands_; std::vector> buffers_; + std::vector> samplers_; std::vector> pipelines_; std::vector> types_; std::vector> formats_; diff --git a/src/vulkan/CMakeLists.txt b/src/vulkan/CMakeLists.txt index b5f11eab0..d06ca0b1d 100644 --- a/src/vulkan/CMakeLists.txt +++ b/src/vulkan/CMakeLists.txt @@ -14,6 +14,7 @@ set(VULKAN_ENGINE_SOURCES buffer_descriptor.cc + buffer_backed_descriptor.cc command_buffer.cc command_pool.cc compute_pipeline.cc @@ -27,6 +28,7 @@ set(VULKAN_ENGINE_SOURCES pipeline.cc push_constant.cc resource.cc + sampler_descriptor.cc transfer_buffer.cc transfer_image.cc vertex_buffer.cc diff --git a/src/vulkan/buffer_backed_descriptor.cc b/src/vulkan/buffer_backed_descriptor.cc new file mode 100644 index 000000000..52b4044e9 --- /dev/null +++ b/src/vulkan/buffer_backed_descriptor.cc @@ -0,0 +1,110 @@ +// Copyright 2019 The Amber 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 "src/vulkan/buffer_backed_descriptor.h" + +#include + +#include "src/vulkan/command_buffer.h" +#include "src/vulkan/device.h" + +namespace amber { +namespace vulkan { + +BufferBackedDescriptor::BufferBackedDescriptor(Buffer* buffer, + DescriptorType type, + Device* device, + uint32_t desc_set, + uint32_t binding) + : Descriptor(type, device, desc_set, binding), amber_buffer_(buffer) {} + +BufferBackedDescriptor::~BufferBackedDescriptor() = default; + +void BufferBackedDescriptor::RecordCopyDataToResourceIfNeeded( + CommandBuffer* command) { + if (!GetResource()) + return; + + if (amber_buffer_ && !amber_buffer_->ValuePtr()->empty()) { + GetResource()->UpdateMemoryWithRawData(*amber_buffer_->ValuePtr()); + amber_buffer_->ValuePtr()->clear(); + } + + GetResource()->CopyToDevice(command); +} + +Result BufferBackedDescriptor::RecordCopyDataToHost(CommandBuffer* command) { + if (!GetResource()) { + return Result( + "Vulkan: BufferBackedDescriptor::RecordCopyDataToHost() no transfer " + "resource"); + } + + GetResource()->CopyToHost(command); + return {}; +} + +Result BufferBackedDescriptor::MoveResourceToBufferOutput() { + if (!GetResource()) { + return Result( + "Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() no " + "transfer" + " resource"); + } + + // Only need to copy the buffer back if we have an attached amber buffer to + // write to. + if (amber_buffer_) { + void* resource_memory_ptr = GetResource()->HostAccessibleMemoryPtr(); + if (!resource_memory_ptr) { + return Result( + "Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() " + "no host accessible memory pointer"); + } + + if (!amber_buffer_->ValuePtr()->empty()) { + return Result( + "Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() " + "output buffer is not empty"); + } + + auto size_in_bytes = GetResource()->GetSizeInBytes(); + amber_buffer_->SetElementCount(size_in_bytes / + amber_buffer_->GetFormat()->SizeInBytes()); + amber_buffer_->ValuePtr()->resize(size_in_bytes); + std::memcpy(amber_buffer_->ValuePtr()->data(), resource_memory_ptr, + size_in_bytes); + } + + return {}; +} + +Result BufferBackedDescriptor::SetSizeInElements(uint32_t element_count) { + if (!amber_buffer_) + return Result("missing amber_buffer for SetSizeInElements call"); + + amber_buffer_->SetSizeInElements(element_count); + return {}; +} + +Result BufferBackedDescriptor::AddToBuffer(const std::vector& values, + uint32_t offset) { + if (!amber_buffer_) + return Result("missing amber_buffer for AddToBuffer call"); + + return amber_buffer_->SetDataWithOffset(values, offset); +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/buffer_backed_descriptor.h b/src/vulkan/buffer_backed_descriptor.h new file mode 100644 index 000000000..4af92398a --- /dev/null +++ b/src/vulkan/buffer_backed_descriptor.h @@ -0,0 +1,60 @@ +// Copyright 2019 The Amber 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. + +#ifndef SRC_VULKAN_BUFFER_BACKED_DESCRIPTOR_H_ +#define SRC_VULKAN_BUFFER_BACKED_DESCRIPTOR_H_ + +#include +#include + +#include "amber/result.h" +#include "amber/value.h" +#include "amber/vulkan_header.h" +#include "src/buffer.h" +#include "src/engine.h" +#include "src/vulkan/descriptor.h" +#include "src/vulkan/resource.h" + +namespace amber { +namespace vulkan { + +class BufferBackedDescriptor : public Descriptor { + public: + BufferBackedDescriptor(Buffer* buffer, + DescriptorType type, + Device* device, + uint32_t desc_set, + uint32_t binding); + ~BufferBackedDescriptor() override; + + Result CreateResourceIfNeeded() override { return {}; } + void RecordCopyDataToResourceIfNeeded(CommandBuffer* command) override; + Result RecordCopyDataToHost(CommandBuffer* command) override; + Result MoveResourceToBufferOutput() override; + virtual Resource* GetResource() = 0; + + Result SetSizeInElements(uint32_t element_count) override; + Result AddToBuffer(const std::vector& values, + uint32_t offset) override; + Buffer* getAmberBuffer() { return amber_buffer_; } + void setAmberBuffer(Buffer* buffer) { amber_buffer_ = buffer; } + + private: + Buffer* amber_buffer_ = nullptr; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_BUFFER_BACKED_DESCRIPTOR_H_ diff --git a/src/vulkan/buffer_descriptor.cc b/src/vulkan/buffer_descriptor.cc index 7b54948d1..da9821ecf 100644 --- a/src/vulkan/buffer_descriptor.cc +++ b/src/vulkan/buffer_descriptor.cc @@ -28,7 +28,7 @@ BufferDescriptor::BufferDescriptor(Buffer* buffer, Device* device, uint32_t desc_set, uint32_t binding) - : Descriptor(buffer, type, device, desc_set, binding) {} + : BufferBackedDescriptor(buffer, type, device, desc_set, binding) {} BufferDescriptor::~BufferDescriptor() = default; @@ -39,12 +39,14 @@ Result BufferDescriptor::CreateResourceIfNeeded() { "only when |transfer_buffer| is empty"); } - if (amber_buffer_ && amber_buffer_->ValuePtr()->empty()) + auto amber_buffer = getAmberBuffer(); + + if (amber_buffer && amber_buffer->ValuePtr()->empty()) return {}; uint32_t size_in_bytes = - amber_buffer_ ? static_cast(amber_buffer_->ValuePtr()->size()) - : 0; + amber_buffer ? static_cast(amber_buffer->ValuePtr()->size()) + : 0; transfer_buffer_ = MakeUnique(device_, size_in_bytes); Result r = transfer_buffer_->Initialize( @@ -59,7 +61,7 @@ Result BufferDescriptor::CreateResourceIfNeeded() { } Result BufferDescriptor::MoveResourceToBufferOutput() { - Result r = Descriptor::MoveResourceToBufferOutput(); + Result r = BufferBackedDescriptor::MoveResourceToBufferOutput(); transfer_buffer_ = nullptr; return r; diff --git a/src/vulkan/buffer_descriptor.h b/src/vulkan/buffer_descriptor.h index 5a41a8bab..3bd31bedf 100644 --- a/src/vulkan/buffer_descriptor.h +++ b/src/vulkan/buffer_descriptor.h @@ -23,7 +23,7 @@ #include "amber/vulkan_header.h" #include "src/buffer.h" #include "src/engine.h" -#include "src/vulkan/descriptor.h" +#include "src/vulkan/buffer_backed_descriptor.h" #include "src/vulkan/transfer_buffer.h" namespace amber { @@ -34,7 +34,7 @@ class Device; /// Stores descriptor set and binding information for storage and uniform /// buffers. -class BufferDescriptor : public Descriptor { +class BufferDescriptor : public BufferBackedDescriptor { public: BufferDescriptor(Buffer* buffer, DescriptorType type, diff --git a/src/vulkan/descriptor.cc b/src/vulkan/descriptor.cc index 7f06c8185..0ee4ba9f4 100644 --- a/src/vulkan/descriptor.cc +++ b/src/vulkan/descriptor.cc @@ -21,16 +21,14 @@ namespace amber { namespace vulkan { -Descriptor::Descriptor(Buffer* buffer, - DescriptorType type, +Descriptor::Descriptor(DescriptorType type, Device* device, uint32_t desc_set, uint32_t binding) : device_(device), type_(type), descriptor_set_(desc_set), - binding_(binding), - amber_buffer_(buffer) {} + binding_(binding) {} Descriptor::~Descriptor() = default; @@ -40,6 +38,8 @@ VkDescriptorType Descriptor::GetVkDescriptorType() const { return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; case DescriptorType::kUniformBuffer: return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + case DescriptorType::kSampler: + return VK_DESCRIPTOR_TYPE_SAMPLER; case DescriptorType::kStorageImage: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; case DescriptorType::kCombinedImageSampler: @@ -50,77 +50,5 @@ VkDescriptorType Descriptor::GetVkDescriptorType() const { } } -void Descriptor::RecordCopyDataToResourceIfNeeded(CommandBuffer* command) { - if (!GetResource()) - return; - - if (amber_buffer_ && !amber_buffer_->ValuePtr()->empty()) { - GetResource()->UpdateMemoryWithRawData(*amber_buffer_->ValuePtr()); - amber_buffer_->ValuePtr()->clear(); - } - - GetResource()->CopyToDevice(command); -} - -Result Descriptor::RecordCopyDataToHost(CommandBuffer* command) { - if (!GetResource()) { - return Result( - "Vulkan: Descriptor::RecordCopyDataToHost() no transfer resource"); - } - - GetResource()->CopyToHost(command); - return {}; -} - -Result Descriptor::MoveResourceToBufferOutput() { - if (!GetResource()) { - return Result( - "Vulkan: Descriptor::MoveResourceToBufferOutput() no transfer" - " resource"); - } - - // Only need to copy the buffer back if we have an attached amber buffer to - // write to. - if (amber_buffer_) { - void* resource_memory_ptr = GetResource()->HostAccessibleMemoryPtr(); - if (!resource_memory_ptr) { - return Result( - "Vulkan: Descriptor::MoveResourceToBufferOutput() " - "no host accessible memory pointer"); - } - - if (!amber_buffer_->ValuePtr()->empty()) { - return Result( - "Vulkan: Descriptor::MoveResourceToBufferOutput() " - "output buffer is not empty"); - } - - auto size_in_bytes = GetResource()->GetSizeInBytes(); - amber_buffer_->SetElementCount(size_in_bytes / - amber_buffer_->GetFormat()->SizeInBytes()); - amber_buffer_->ValuePtr()->resize(size_in_bytes); - std::memcpy(amber_buffer_->ValuePtr()->data(), resource_memory_ptr, - size_in_bytes); - } - - return {}; -} - -Result Descriptor::SetSizeInElements(uint32_t element_count) { - if (!amber_buffer_) - return Result("missing amber_buffer for SetSizeInElements call"); - - amber_buffer_->SetSizeInElements(element_count); - return {}; -} - -Result Descriptor::AddToBuffer(const std::vector& values, - uint32_t offset) { - if (!amber_buffer_) - return Result("missing amber_buffer for AddToBuffer call"); - - return amber_buffer_->SetDataWithOffset(values, offset); -} - } // namespace vulkan } // namespace amber diff --git a/src/vulkan/descriptor.h b/src/vulkan/descriptor.h index 2e8e5b09d..0a887e43a 100644 --- a/src/vulkan/descriptor.h +++ b/src/vulkan/descriptor.h @@ -36,31 +36,29 @@ enum class DescriptorType : uint8_t { kUniformBuffer, kStorageImage, kSampledImage, - kCombinedImageSampler + kCombinedImageSampler, + kSampler }; class Descriptor { public: - Descriptor(Buffer* buffer, - DescriptorType type, + Descriptor(DescriptorType type, Device* device, uint32_t desc_set, uint32_t binding); virtual ~Descriptor(); + virtual void UpdateDescriptorSetIfNeeded(VkDescriptorSet descriptor_set) = 0; + virtual Result CreateResourceIfNeeded() = 0; + virtual void RecordCopyDataToResourceIfNeeded(CommandBuffer*) {} + virtual Result RecordCopyDataToHost(CommandBuffer*) { return {}; } + virtual Result MoveResourceToBufferOutput() { return {}; } + virtual Result SetSizeInElements(uint32_t) { return {}; } + virtual Result AddToBuffer(const std::vector&, uint32_t) { return {}; } uint32_t GetDescriptorSet() const { return descriptor_set_; } uint32_t GetBinding() const { return binding_; } VkDescriptorType GetVkDescriptorType() const; - virtual Result CreateResourceIfNeeded() = 0; - virtual void RecordCopyDataToResourceIfNeeded(CommandBuffer* command); - virtual Result RecordCopyDataToHost(CommandBuffer* command); - virtual Result MoveResourceToBufferOutput(); - virtual void UpdateDescriptorSetIfNeeded(VkDescriptorSet descriptor_set) = 0; - virtual Resource* GetResource() = 0; - - Result SetSizeInElements(uint32_t element_count); - Result AddToBuffer(const std::vector& values, uint32_t offset); bool IsStorageBuffer() const { return type_ == DescriptorType::kStorageBuffer; } @@ -69,12 +67,11 @@ class Descriptor { } protected: - bool is_descriptor_set_update_needed_ = false; Device* device_ = nullptr; DescriptorType type_ = DescriptorType::kStorageBuffer; uint32_t descriptor_set_ = 0; uint32_t binding_ = 0; - Buffer* amber_buffer_ = nullptr; + bool is_descriptor_set_update_needed_ = false; }; } // namespace vulkan diff --git a/src/vulkan/engine_vulkan.cc b/src/vulkan/engine_vulkan.cc index 8dec4b4c7..acaabe89f 100644 --- a/src/vulkan/engine_vulkan.cc +++ b/src/vulkan/engine_vulkan.cc @@ -230,14 +230,15 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { for (const auto& buf_info : pipeline->GetBuffers()) { auto type = BufferCommand::BufferType::kSSBO; - if (buf_info.buffer->GetBufferType() == BufferType::kStorageImage) { + if (buf_info.type == BufferType::kStorageImage) { type = BufferCommand::BufferType::kStorageImage; - } else if (buf_info.buffer->GetBufferType() == BufferType::kUniform) { + } else if (buf_info.type == BufferType::kSampledImage) { + type = BufferCommand::BufferType::kSampledImage; + } else if (buf_info.type == BufferType::kUniform) { type = BufferCommand::BufferType::kUniform; - } else if (buf_info.buffer->GetBufferType() != BufferType::kStorage) { + } else if (buf_info.type != BufferType::kStorage) { return Result("Vulkan: CreatePipeline - unknown buffer type: " + - std::to_string(static_cast( - buf_info.buffer->GetBufferType()))); + std::to_string(static_cast(buf_info.type))); } auto cmd = MakeUnique(type, pipeline); @@ -245,7 +246,18 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { cmd->SetBinding(buf_info.binding); cmd->SetBuffer(buf_info.buffer); - r = info.vk_pipeline->AddDescriptor(cmd.get()); + r = info.vk_pipeline->AddBufferDescriptor(cmd.get()); + if (!r.IsSuccess()) + return r; + } + + for (const auto& sampler_info : pipeline->GetSamplers()) { + auto cmd = MakeUnique(pipeline); + cmd->SetDescriptorSet(sampler_info.descriptor_set); + cmd->SetBinding(sampler_info.binding); + cmd->SetSampler(sampler_info.sampler); + + r = info.vk_pipeline->AddSamplerDescriptor(cmd.get()); if (!r.IsSuccess()) return r; } @@ -485,7 +497,7 @@ Result EngineVulkan::DoBuffer(const BufferCommand* cmd) { "device"); } auto& info = pipeline_map_[cmd->GetPipeline()]; - return info.vk_pipeline->AddDescriptor(cmd); + return info.vk_pipeline->AddBufferDescriptor(cmd); } } // namespace vulkan diff --git a/src/vulkan/image_descriptor.cc b/src/vulkan/image_descriptor.cc index 101ab6feb..696d35f2b 100644 --- a/src/vulkan/image_descriptor.cc +++ b/src/vulkan/image_descriptor.cc @@ -24,7 +24,7 @@ ImageDescriptor::ImageDescriptor(Buffer* buffer, Device* device, uint32_t desc_set, uint32_t binding) - : Descriptor(buffer, type, device, desc_set, binding) {} + : BufferBackedDescriptor(buffer, type, device, desc_set, binding) {} ImageDescriptor::~ImageDescriptor() = default; @@ -32,12 +32,18 @@ void ImageDescriptor::RecordCopyDataToResourceIfNeeded(CommandBuffer* command) { transfer_image_->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT); - Descriptor::RecordCopyDataToResourceIfNeeded(command); + BufferBackedDescriptor::RecordCopyDataToResourceIfNeeded(command); if (type_ == DescriptorType::kStorageImage) { // Change to general layout as it's required for storage images. transfer_image_->ImageBarrier(command, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + } else { + // Use the earliest shader stage as we don't know which stage the image is + // used in. + transfer_image_->ImageBarrier(command, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT); } } @@ -48,12 +54,14 @@ Result ImageDescriptor::CreateResourceIfNeeded() { "only when |transfer_image| is empty"); } - if (amber_buffer_ && amber_buffer_->ValuePtr()->empty()) + auto amber_buffer = getAmberBuffer(); + + if (amber_buffer && amber_buffer->ValuePtr()->empty()) return {}; transfer_image_ = MakeUnique( - device_, *amber_buffer_->GetFormat(), VK_IMAGE_ASPECT_COLOR_BIT, - amber_buffer_->GetWidth(), amber_buffer_->GetHeight(), 1u); + device_, *amber_buffer->GetFormat(), VK_IMAGE_ASPECT_COLOR_BIT, + amber_buffer->GetWidth(), amber_buffer->GetHeight(), 1u); VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; @@ -78,13 +86,13 @@ Result ImageDescriptor::RecordCopyDataToHost(CommandBuffer* command) { transfer_image_->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT); - Descriptor::RecordCopyDataToHost(command); + BufferBackedDescriptor::RecordCopyDataToHost(command); return {}; } Result ImageDescriptor::MoveResourceToBufferOutput() { - Result r = Descriptor::MoveResourceToBufferOutput(); + Result r = BufferBackedDescriptor::MoveResourceToBufferOutput(); transfer_image_ = nullptr; return r; @@ -101,7 +109,7 @@ void ImageDescriptor::UpdateDescriptorSetIfNeeded( layout = VK_IMAGE_LAYOUT_GENERAL; VkDescriptorImageInfo image_info = { - nullptr, // TODO(asuonpaa): Add sampler here later if used + VK_NULL_HANDLE, // TODO(asuonpaa): Add sampler here later if used transfer_image_->GetVkImageView(), layout}; VkWriteDescriptorSet write = VkWriteDescriptorSet(); diff --git a/src/vulkan/image_descriptor.h b/src/vulkan/image_descriptor.h index fd3905284..e202f8634 100644 --- a/src/vulkan/image_descriptor.h +++ b/src/vulkan/image_descriptor.h @@ -18,13 +18,13 @@ #include #include -#include "src/vulkan/descriptor.h" +#include "src/vulkan/buffer_backed_descriptor.h" #include "src/vulkan/transfer_image.h" namespace amber { namespace vulkan { -class ImageDescriptor : public Descriptor { +class ImageDescriptor : public BufferBackedDescriptor { public: ImageDescriptor(Buffer* buffer, DescriptorType type, diff --git a/src/vulkan/pipeline.cc b/src/vulkan/pipeline.cc index 7897add48..ede682991 100644 --- a/src/vulkan/pipeline.cc +++ b/src/vulkan/pipeline.cc @@ -26,6 +26,7 @@ #include "src/vulkan/device.h" #include "src/vulkan/graphics_pipeline.h" #include "src/vulkan/image_descriptor.h" +#include "src/vulkan/sampler_descriptor.h" namespace amber { namespace vulkan { @@ -240,15 +241,11 @@ Result Pipeline::AddPushConstantBuffer(const Buffer* buf, uint32_t offset) { return push_constant_->AddBuffer(buf, offset); } -Result Pipeline::AddDescriptor(const BufferCommand* cmd) { - if (cmd == nullptr) - return Result("Pipeline::AddDescriptor BufferCommand is nullptr"); - if (cmd->IsPushConstant()) - return AddPushConstantBuffer(cmd->GetBuffer(), cmd->GetOffset()); - if (!cmd->IsSSBO() && !cmd->IsUniform() && !cmd->IsStorageImage()) - return Result("Pipeline::AddDescriptor not supported buffer type"); +Result Pipeline::GetDescriptorSlot(uint32_t desc_set, + uint32_t binding, + Descriptor** desc) { + *desc = nullptr; - const uint32_t desc_set = cmd->GetDescriptorSet(); if (desc_set >= descriptor_set_info_.size()) { for (size_t i = descriptor_set_info_.size(); i <= static_cast(desc_set); ++i) { @@ -268,18 +265,42 @@ Result Pipeline::AddDescriptor(const BufferCommand* cmd) { descriptor_set_info_[desc_set].empty = false; auto& descriptors = descriptor_set_info_[desc_set].descriptors; - Descriptor* desc = nullptr; for (auto& descriptor : descriptors) { - if (descriptor->GetBinding() == cmd->GetBinding()) - desc = descriptor.get(); + if (descriptor->GetBinding() == binding) + *desc = descriptor.get(); } + return {}; +} + +Result Pipeline::AddBufferDescriptor(const BufferCommand* cmd) { + if (cmd == nullptr) + return Result("Pipeline::AddBufferDescriptor BufferCommand is nullptr"); + if (cmd->IsPushConstant()) + return AddPushConstantBuffer(cmd->GetBuffer(), cmd->GetOffset()); + if (!cmd->IsSSBO() && !cmd->IsUniform() && !cmd->IsStorageImage() && + !cmd->IsSampledImage()) + return Result("Pipeline::AddBufferDescriptor not supported buffer type"); + + Descriptor* desc; + Result r = + GetDescriptorSlot(cmd->GetDescriptorSet(), cmd->GetBinding(), &desc); + if (!r.IsSuccess()) + return r; + + auto& descriptors = descriptor_set_info_[cmd->GetDescriptorSet()].descriptors; + if (desc == nullptr) { if (cmd->IsStorageImage()) { auto image_desc = MakeUnique( cmd->GetBuffer(), DescriptorType::kStorageImage, device_, cmd->GetDescriptorSet(), cmd->GetBinding()); descriptors.push_back(std::move(image_desc)); + } else if (cmd->IsSampledImage()) { + auto image_desc = MakeUnique( + cmd->GetBuffer(), DescriptorType::kSampledImage, device_, + cmd->GetDescriptorSet(), cmd->GetBinding()); + descriptors.push_back(std::move(image_desc)); } else { auto desc_type = cmd->IsSSBO() ? DescriptorType::kStorageBuffer : DescriptorType::kUniformBuffer; @@ -294,22 +315,24 @@ Result Pipeline::AddDescriptor(const BufferCommand* cmd) { if (cmd->IsSSBO() && !desc->IsStorageBuffer()) { return Result( - "Vulkan::AddDescriptor BufferCommand for SSBO uses wrong descriptor " + "Vulkan::AddBufferDescriptor BufferCommand for SSBO uses wrong " + "descriptor " "set and binding"); } if (cmd->IsUniform() && !desc->IsUniformBuffer()) { return Result( - "Vulkan::AddDescriptor BufferCommand for UBO uses wrong descriptor set " + "Vulkan::AddBufferDescriptor BufferCommand for UBO uses wrong " + "descriptor set " "and binding"); } if (cmd->GetValues().empty()) { - Result r = desc->SetSizeInElements(cmd->GetBuffer()->ElementCount()); + r = desc->SetSizeInElements(cmd->GetBuffer()->ElementCount()); if (!r.IsSuccess()) return r; } else { - Result r = desc->AddToBuffer(cmd->GetValues(), cmd->GetOffset()); + r = desc->AddToBuffer(cmd->GetValues(), cmd->GetOffset()); if (!r.IsSuccess()) return r; } @@ -317,6 +340,30 @@ Result Pipeline::AddDescriptor(const BufferCommand* cmd) { return {}; } +Result Pipeline::AddSamplerDescriptor(const SamplerCommand* cmd) { + if (cmd == nullptr) + return Result("Pipeline::AddSamplerDescriptor SamplerCommand is nullptr"); + + Descriptor* desc; + Result r = + GetDescriptorSlot(cmd->GetDescriptorSet(), cmd->GetBinding(), &desc); + if (!r.IsSuccess()) + return r; + + auto& descriptors = descriptor_set_info_[cmd->GetDescriptorSet()].descriptors; + + if (desc == nullptr) { + auto sampler_desc = MakeUnique( + cmd->GetSampler(), DescriptorType::kSampler, device_, + cmd->GetDescriptorSet(), cmd->GetBinding()); + descriptors.push_back(std::move(sampler_desc)); + + desc = descriptors.back().get(); + } + + return {}; +} + Result Pipeline::SendDescriptorDataToDeviceIfNeeded() { { CommandBufferGuard guard(GetCommandBuffer()); diff --git a/src/vulkan/pipeline.h b/src/vulkan/pipeline.h index 3ff6e0879..23a18e128 100644 --- a/src/vulkan/pipeline.h +++ b/src/vulkan/pipeline.h @@ -24,8 +24,8 @@ #include "amber/vulkan_header.h" #include "src/cast_hash.h" #include "src/engine.h" +#include "src/vulkan/buffer_backed_descriptor.h" #include "src/vulkan/command_buffer.h" -#include "src/vulkan/descriptor.h" #include "src/vulkan/push_constant.h" namespace amber { @@ -49,7 +49,8 @@ class Pipeline { GraphicsPipeline* AsGraphics(); ComputePipeline* AsCompute(); - Result AddDescriptor(const BufferCommand*); + Result AddBufferDescriptor(const BufferCommand*); + Result AddSamplerDescriptor(const SamplerCommand*); /// Add |buffer| data to the push constants at |offset|. Result AddPushConstantBuffer(const Buffer* buf, uint32_t offset); @@ -76,6 +77,9 @@ class Pipeline { /// Initializes the pipeline. Result Initialize(CommandPool* pool); + Result GetDescriptorSlot(uint32_t desc_set, + uint32_t binding, + Descriptor** desc); void UpdateDescriptorSetsIfNeeded(); Result SendDescriptorDataToDeviceIfNeeded(); diff --git a/src/vulkan/sampler_descriptor.cc b/src/vulkan/sampler_descriptor.cc new file mode 100644 index 000000000..430f7ccaa --- /dev/null +++ b/src/vulkan/sampler_descriptor.cc @@ -0,0 +1,123 @@ +// Copyright 2019 The Amber 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 "src/vulkan/sampler_descriptor.h" +#include "src/vulkan/device.h" +#include "src/vulkan/resource.h" + +namespace amber { +namespace vulkan { +namespace { + +VkSamplerAddressMode GetVkAddressMode(AddressMode mode) { + switch (mode) { + case AddressMode::kRepeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case AddressMode::kMirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case AddressMode::kClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case AddressMode::kClampToBorder: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + default: + assert(mode == AddressMode::kMirrorClampToEdge); + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + } +} + +VkBorderColor GetVkBorderColor(BorderColor color) { + switch (color) { + case BorderColor::kFloatTransparentBlack: + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + case BorderColor::kIntTransparentBlack: + return VK_BORDER_COLOR_INT_TRANSPARENT_BLACK; + case BorderColor::kFloatOpaqueBlack: + return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + case BorderColor::kIntOpaqueBlack: + return VK_BORDER_COLOR_INT_OPAQUE_BLACK; + case BorderColor::kFloatOpaqueWhite: + return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + default: + assert(color == BorderColor::kIntOpaqueWhite); + return VK_BORDER_COLOR_INT_OPAQUE_WHITE; + } +} + +} // namespace + +SamplerDescriptor::SamplerDescriptor(Sampler* sampler, + DescriptorType type, + Device* device, + uint32_t desc_set, + uint32_t binding) + : Descriptor(type, device, desc_set, binding), + amber_sampler_(sampler), + sampler_(VK_NULL_HANDLE) {} + +SamplerDescriptor::~SamplerDescriptor() { + if (sampler_ != VK_NULL_HANDLE) { + device_->GetPtrs()->vkDestroySampler(device_->GetVkDevice(), sampler_, + nullptr); + } +} + +Result SamplerDescriptor::CreateResourceIfNeeded() { + VkSamplerCreateInfo sampler_info = VkSamplerCreateInfo(); + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.magFilter = amber_sampler_->GetMagFilter() == FilterType::kLinear + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; + sampler_info.minFilter = amber_sampler_->GetMinFilter() == FilterType::kLinear + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; + sampler_info.mipmapMode = + amber_sampler_->GetMipmapMode() == FilterType::kLinear + ? VK_SAMPLER_MIPMAP_MODE_LINEAR + : VK_SAMPLER_MIPMAP_MODE_NEAREST; + sampler_info.addressModeU = + GetVkAddressMode(amber_sampler_->GetAddressModeU()); + sampler_info.addressModeV = + GetVkAddressMode(amber_sampler_->GetAddressModeV()); + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.borderColor = GetVkBorderColor(amber_sampler_->GetBorderColor()); + sampler_info.maxLod = 1.0f; + + if (device_->GetPtrs()->vkCreateSampler(device_->GetVkDevice(), &sampler_info, + nullptr, &sampler_) != VK_SUCCESS) { + return Result("Vulkan::Calling vkCreateSampler Fail"); + } + + return {}; +} + +void SamplerDescriptor::UpdateDescriptorSetIfNeeded( + VkDescriptorSet descriptor_set) { + VkDescriptorImageInfo image_info = {sampler_, VK_NULL_HANDLE, + VK_IMAGE_LAYOUT_GENERAL}; + + VkWriteDescriptorSet write = VkWriteDescriptorSet(); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptor_set; + write.dstBinding = binding_; + write.dstArrayElement = 0; + write.descriptorCount = 1; + write.descriptorType = GetVkDescriptorType(); + write.pImageInfo = &image_info; + + device_->GetPtrs()->vkUpdateDescriptorSets(device_->GetVkDevice(), 1, &write, + 0, nullptr); +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/sampler_descriptor.h b/src/vulkan/sampler_descriptor.h new file mode 100644 index 000000000..a49d032ee --- /dev/null +++ b/src/vulkan/sampler_descriptor.h @@ -0,0 +1,44 @@ +// Copyright 2019 The Amber 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. + +#ifndef SRC_VULKAN_SAMPLER_DESCRIPTOR_H_ +#define SRC_VULKAN_SAMPLER_DESCRIPTOR_H_ + +#include "src/vulkan/descriptor.h" +#include "src/vulkan/transfer_image.h" + +namespace amber { +namespace vulkan { + +class SamplerDescriptor : public Descriptor { + public: + SamplerDescriptor(Sampler* sampler, + DescriptorType type, + Device* device, + uint32_t desc_set, + uint32_t binding); + ~SamplerDescriptor() override; + + void UpdateDescriptorSetIfNeeded(VkDescriptorSet descriptor_set) override; + Result CreateResourceIfNeeded() override; + + private: + Sampler* amber_sampler_; + VkSampler sampler_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_SAMPLER_DESCRIPTOR_H_ diff --git a/src/vulkan/vk-funcs.inc b/src/vulkan/vk-funcs.inc index 36d286421..22c37e86a 100644 --- a/src/vulkan/vk-funcs.inc +++ b/src/vulkan/vk-funcs.inc @@ -32,6 +32,7 @@ AMBER_VK_FUNC(vkCreateImage) AMBER_VK_FUNC(vkCreateImageView) AMBER_VK_FUNC(vkCreatePipelineLayout) AMBER_VK_FUNC(vkCreateRenderPass) +AMBER_VK_FUNC(vkCreateSampler) AMBER_VK_FUNC(vkCreateShaderModule) AMBER_VK_FUNC(vkDestroyBuffer) AMBER_VK_FUNC(vkDestroyBufferView) @@ -45,6 +46,7 @@ AMBER_VK_FUNC(vkDestroyImageView) AMBER_VK_FUNC(vkDestroyPipeline) AMBER_VK_FUNC(vkDestroyPipelineLayout) AMBER_VK_FUNC(vkDestroyRenderPass) +AMBER_VK_FUNC(vkDestroySampler) AMBER_VK_FUNC(vkDestroyShaderModule) AMBER_VK_FUNC(vkEndCommandBuffer) AMBER_VK_FUNC(vkFreeCommandBuffers) diff --git a/tests/cases/address_modes_float.amber b/tests/cases/address_modes_float.amber new file mode 100755 index 000000000..31035c903 --- /dev/null +++ b/tests/cases/address_modes_float.amber @@ -0,0 +1,138 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) texture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(sampler2D(tex, tex_sampler), texcoords_in); +} +END + +BUFFER texture FORMAT R8G8B8A8_UNORM +BUFFER framebuffer FORMAT B8G8R8A8_UNORM + +# Define samplers for all floating point border colors. +SAMPLER sampler_float_opaque_white \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR float_opaque_white + +SAMPLER sampler_float_opaque_black \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR float_opaque_black + +SAMPLER sampler_float_transparent_black \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR float_transparent_black + +BUFFER position DATA_TYPE vec2 DATA +-1.0 -1.0 + 0.0 -1.0 + 0.0 0.0 +-1.0 0.0 + + 0.0 -1.0 + 1.0 -1.0 + 1.0 0.0 + 0.0 0.0 + +-1.0 0.0 + 0.0 0.0 + 0.0 1.0 +-1.0 1.0 +END +BUFFER texcoords DATA_TYPE vec2 DATA +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 + +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 + +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 +END + +PIPELINE graphics pipeline_texgen + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline_float_opaque_white + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler_float_opaque_white DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer AS color LOCATION 0 +END + +DERIVE_PIPELINE pipeline_float_opaque_black FROM pipeline_float_opaque_white + BIND SAMPLER sampler_float_opaque_black DESCRIPTOR_SET 0 BINDING 1 +END + +DERIVE_PIPELINE pipeline_float_transparent_black FROM pipeline_float_opaque_white + BIND SAMPLER sampler_float_transparent_black DESCRIPTOR_SET 0 BINDING 1 +END + +# Generate texture: a rectangle at the lower right corner. +CLEAR_COLOR pipeline_texgen 0 0 255 255 +CLEAR pipeline_texgen +RUN pipeline_texgen DRAW_RECT POS 128 128 SIZE 128 128 + +# Draw the texture with coordinates going beyond 0 and 1 to trigger border color. +RUN pipeline_float_opaque_white DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 +RUN pipeline_float_opaque_black DRAW_ARRAY AS TRIANGLE_FAN START_IDX 4 COUNT 4 +RUN pipeline_float_transparent_black DRAW_ARRAY AS TRIANGLE_FAN START_IDX 8 COUNT 4 + +EXPECT framebuffer IDX 1 1 SIZE 1 1 EQ_RGBA 255 255 255 255 +EXPECT framebuffer IDX 129 1 SIZE 1 1 EQ_RGBA 0 0 0 255 +EXPECT framebuffer IDX 1 129 SIZE 1 1 EQ_RGBA 0 0 0 0 diff --git a/tests/cases/address_modes_int.amber b/tests/cases/address_modes_int.amber new file mode 100755 index 000000000..36852c5b1 --- /dev/null +++ b/tests/cases/address_modes_int.amber @@ -0,0 +1,138 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out uvec4 color_out; +void main() { + color_out = uvec4(255, 0.0, 0.0, 255); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) utexture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(usampler2D(tex, tex_sampler), texcoords_in); +} +END + +BUFFER texture FORMAT R8G8B8A8_UINT +BUFFER framebuffer FORMAT B8G8R8A8_UNORM + +# Define samplers for all integer border colors. +SAMPLER sampler_int_opaque_white \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR int_opaque_white + +SAMPLER sampler_int_opaque_black \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR int_opaque_black + +SAMPLER sampler_int_transparent_black \ + ADDRESS_MODE_U clamp_to_border \ + ADDRESS_MODE_V clamp_to_border \ + BORDER_COLOR int_transparent_black + +BUFFER position DATA_TYPE vec2 DATA +-1.0 -1.0 + 0.0 -1.0 + 0.0 0.0 +-1.0 0.0 + + 0.0 -1.0 + 1.0 -1.0 + 1.0 0.0 + 0.0 0.0 + +-1.0 0.0 + 0.0 0.0 + 0.0 1.0 +-1.0 1.0 +END +BUFFER texcoords DATA_TYPE vec2 DATA +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 + +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 + +-1.0 -1.0 + 2.0 -1.0 + 2.0 2.0 +-1.0 2.0 +END + +PIPELINE graphics pipeline_texgen + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline_int_opaque_white + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler_int_opaque_white DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer AS color LOCATION 0 +END + +DERIVE_PIPELINE pipeline_int_opaque_black FROM pipeline_int_opaque_white + BIND SAMPLER sampler_int_opaque_black DESCRIPTOR_SET 0 BINDING 1 +END + +DERIVE_PIPELINE pipeline_int_transparent_black FROM pipeline_int_opaque_white + BIND SAMPLER sampler_int_transparent_black DESCRIPTOR_SET 0 BINDING 1 +END + +# Generate texture: a rectangle at the lower right corner. +CLEAR_COLOR pipeline_texgen 0 0 255 255 +CLEAR pipeline_texgen +RUN pipeline_texgen DRAW_RECT POS 128 128 SIZE 128 128 + +# Draw the texture with coordinates going beyond 0 and 1 to trigger border color. +RUN pipeline_int_opaque_white DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 +RUN pipeline_int_opaque_black DRAW_ARRAY AS TRIANGLE_FAN START_IDX 4 COUNT 4 +RUN pipeline_int_transparent_black DRAW_ARRAY AS TRIANGLE_FAN START_IDX 8 COUNT 4 + +EXPECT framebuffer IDX 1 1 SIZE 1 1 EQ_RGBA 255 255 255 255 +EXPECT framebuffer IDX 129 1 SIZE 1 1 EQ_RGBA 0 0 0 255 +EXPECT framebuffer IDX 1 129 SIZE 1 1 EQ_RGBA 0 0 0 0 diff --git a/tests/cases/draw_rect_multiple_pipeline.amber b/tests/cases/draw_rect_multiple_pipeline.amber index 3f9cbb00f..40efab091 100644 --- a/tests/cases/draw_rect_multiple_pipeline.amber +++ b/tests/cases/draw_rect_multiple_pipeline.amber @@ -39,6 +39,6 @@ RUN my_pipeline DRAW_RECT POS 0 0 SIZE 250 250 RUN pipeline2 DRAW_RECT POS 250 250 SIZE 250 250 RUN pipeline3 DRAW_RECT POS 0 250 SIZE 250 250 -EXPECT framebuffer IDX 250 250 SIZE 250 250 EQ_RGBA 0 127 0 204 -EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 0 127 0 204 -EXPECT framebuffer IDX 0 250 SIZE 250 250 EQ_RGBA 0 127 0 204 +EXPECT framebuffer IDX 250 250 SIZE 250 250 EQ_RGBA 0 127 0 204 TOLERANCE 0 1 0 1 +EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 0 127 0 204 TOLERANCE 0 1 0 1 +EXPECT framebuffer IDX 0 250 SIZE 250 250 EQ_RGBA 0 127 0 204 TOLERANCE 0 1 0 1 diff --git a/tests/cases/draw_rectangles.amber b/tests/cases/draw_rectangles.amber index 2807eb58d..21f0260c7 100644 --- a/tests/cases/draw_rectangles.amber +++ b/tests/cases/draw_rectangles.amber @@ -75,4 +75,4 @@ RUN pipeline4 DRAW_RECT POS 400 300 SIZE 400 300 EXPECT frame IDX 0 0 SIZE 400 300 EQ_RGBA 255 0 0 255 EXPECT frame IDX 0 300 SIZE 400 300 EQ_RGBA 0 255 0 255 EXPECT frame IDX 400 0 SIZE 400 300 EQ_RGBA 0 0 255 255 -EXPECT frame IDX 400 300 SIZE 400 300 EQ_RGBA 127 0 127 255 +EXPECT frame IDX 400 300 SIZE 400 300 EQ_RGBA 127 0 127 255 TOLERANCE 1 0 1 0 diff --git a/tests/cases/draw_sampled_image.amber b/tests/cases/draw_sampled_image.amber new file mode 100644 index 000000000..7e2773653 --- /dev/null +++ b/tests/cases/draw_sampled_image.amber @@ -0,0 +1,115 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) texture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(sampler2D(tex, tex_sampler), texcoords_in); +} +END + +SHADER compute compute_shader GLSL +#version 430 +layout(local_size_x=16,local_size_y=16) in; +uniform layout (set=0, binding=0, rgba8) image2D texture; +void main () { + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + vec4 color = imageLoad(texture, uv) + vec4(0, 1.0, 0, 0); + imageStore(texture, uv, color); +} +END + +BUFFER texture FORMAT R8G8B8A8_UNORM +BUFFER framebuffer FORMAT B8G8R8A8_UNORM +SAMPLER sampler +BUFFER position DATA_TYPE vec2 DATA +-0.75 -0.75 + 0.75 -0.75 + 0.75 0.75 +-0.75 0.75 +END +BUFFER texcoords DATA_TYPE vec2 DATA +0.0 0.0 +2.0 0.0 +2.0 2.0 +0.0 2.0 +END + +PIPELINE graphics pipeline1 + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline2 + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer AS color LOCATION 0 +END + +PIPELINE compute pipeline3 + ATTACH compute_shader + BIND BUFFER texture AS storage_image DESCRIPTOR_SET 0 BINDING 0 + FRAMEBUFFER_SIZE 256 256 +END + +# Generate a texture with a quad at the lower right corner. +CLEAR_COLOR pipeline1 0 0 255 255 +CLEAR pipeline1 +RUN pipeline1 DRAW_RECT POS 128 128 SIZE 128 128 + +# Add green color to a 128x128 quad. +RUN pipeline3 8 8 1 + +# Draw the texture using a default sampler. +CLEAR_COLOR pipeline2 0 255 0 255 +CLEAR pipeline2 +RUN pipeline2 DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 + +EXPECT framebuffer IDX 1 1 SIZE 1 1 EQ_RGBA 0 255 0 255 +EXPECT framebuffer IDX 33 33 SIZE 1 1 EQ_RGBA 0 255 255 255 +EXPECT framebuffer IDX 81 81 SIZE 1 1 EQ_RGBA 255 0 0 255 +EXPECT framebuffer IDX 81 33 SIZE 1 1 EQ_RGBA 0 0 255 255 diff --git a/tests/cases/graphics_push_constants.amber b/tests/cases/graphics_push_constants.amber index 9e025f659..22cfef999 100644 --- a/tests/cases/graphics_push_constants.amber +++ b/tests/cases/graphics_push_constants.amber @@ -55,5 +55,5 @@ END CLEAR pipeline RUN pipeline DRAW_RECT POS 0 0 SIZE 250 250 -EXPECT framebuffer IDX 0 0 SIZE 250 125 EQ_RGBA 127 127 127 255 +EXPECT framebuffer IDX 0 0 SIZE 250 125 EQ_RGBA 127 127 127 255 TOLERANCE 1 1 1 0 EXPECT framebuffer IDX 0 125 SIZE 250 125 EQ_RGBA 0 0 0 0 diff --git a/tests/cases/magfilter_linear.amber b/tests/cases/magfilter_linear.amber new file mode 100644 index 000000000..cab8d6ab3 --- /dev/null +++ b/tests/cases/magfilter_linear.amber @@ -0,0 +1,96 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) texture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(sampler2D(tex, tex_sampler), texcoords_in); +} +END + +BUFFER texture FORMAT R8G8B8A8_UNORM +BUFFER framebuffer FORMAT B8G8R8A8_UNORM +SAMPLER sampler MAG_FILTER linear +BUFFER position DATA_TYPE vec2 DATA +-0.75 -0.75 + 0.75 -0.75 + 0.75 0.75 +-0.75 0.75 +END +BUFFER texcoords DATA_TYPE vec2 DATA +0.0 0.0 +2.0 0.0 +2.0 2.0 +0.0 2.0 +END + +PIPELINE graphics pipeline1 + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 2 2 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline2 + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer AS color LOCATION 0 +END + +# Generate a 2x2 texture with a one pixel sized chessboard pattern. +CLEAR_COLOR pipeline1 0 0 255 255 +CLEAR pipeline1 +RUN pipeline1 DRAW_RECT POS 0 0 SIZE 1 1 +RUN pipeline1 DRAW_RECT POS 1 1 SIZE 1 1 + +# Draw a textured quad with linear magnification. +CLEAR_COLOR pipeline2 0 255 0 255 +CLEAR pipeline2 +RUN pipeline2 DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 + +EXPECT framebuffer IDX 1 1 SIZE 1 1 EQ_RGBA 0 255 0 255 +# Sample a pixel between two upsampled texels. The result should be roughly +# a mean value of the original colors. +EXPECT framebuffer IDX 80 80 SIZE 1 1 EQ_RGBA 128 0 128 255 TOLERANCE 3 0 3 0 diff --git a/tests/cases/magfilter_nearest.amber b/tests/cases/magfilter_nearest.amber new file mode 100644 index 000000000..05cff7ab3 --- /dev/null +++ b/tests/cases/magfilter_nearest.amber @@ -0,0 +1,97 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) texture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(sampler2D(tex, tex_sampler), texcoords_in); +} +END + +BUFFER texture FORMAT R8G8B8A8_UNORM +BUFFER framebuffer FORMAT B8G8R8A8_UNORM +SAMPLER sampler MAG_FILTER nearest +BUFFER position DATA_TYPE vec2 DATA +-0.75 -0.75 + 0.75 -0.75 + 0.75 0.75 +-0.75 0.75 +END +BUFFER texcoords DATA_TYPE vec2 DATA +0.0 0.0 +2.0 0.0 +2.0 2.0 +0.0 2.0 +END + +PIPELINE graphics pipeline1 + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 2 2 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline2 + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer AS color LOCATION 0 +END + +# Generate a 2x2 texture with a one pixel sized chessboard pattern. +CLEAR_COLOR pipeline1 0 0 255 255 +CLEAR pipeline1 +RUN pipeline1 DRAW_RECT POS 0 0 SIZE 1 1 +RUN pipeline1 DRAW_RECT POS 1 1 SIZE 1 1 + +# Draw a textured quad with nearest magnification. +CLEAR_COLOR pipeline2 0 255 0 255 +CLEAR pipeline2 +RUN pipeline2 DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 + +EXPECT framebuffer IDX 1 1 SIZE 1 1 EQ_RGBA 0 255 0 255 +# With nearest magnification filter the chessboard patter should +# remain intact. Sample inside two different colored rectangles. +EXPECT framebuffer IDX 81 81 SIZE 1 1 EQ_RGBA 255 0 0 255 +EXPECT framebuffer IDX 129 81 SIZE 1 1 EQ_RGBA 0 0 255 255 diff --git a/tests/cases/matrices_uniform_draw.amber b/tests/cases/matrices_uniform_draw.amber index 05466e9db..0dd6fd927 100644 --- a/tests/cases/matrices_uniform_draw.amber +++ b/tests/cases/matrices_uniform_draw.amber @@ -895,13 +895,13 @@ CLEAR gfz_pipeline RUN gfz_pipeline DRAW_RECT POS 0 0 SIZE 256 256 EXPECT framebuffer IDX 0 0 SIZE 85 85 EQ_RGBA 0 0 0 255 -EXPECT framebuffer IDX 85 0 SIZE 85 85 EQ_RGBA 96 96 96 255 -EXPECT framebuffer IDX 171 0 SIZE 85 85 EQ_RGBA 127 127 127 255 +EXPECT framebuffer IDX 85 0 SIZE 85 85 EQ_RGBA 96 96 96 255 TOLERANCE 1 1 1 0 +EXPECT framebuffer IDX 171 0 SIZE 85 85 EQ_RGBA 127 127 127 255 TOLERANCE 1 1 1 0 -EXPECT framebuffer IDX 0 85 SIZE 85 85 EQ_RGBA 96 96 96 255 -EXPECT framebuffer IDX 85 85 SIZE 85 85 EQ_RGBA 143 143 143 255 -EXPECT framebuffer IDX 171 85 SIZE 85 85 EQ_RGBA 191 191 191 255 +EXPECT framebuffer IDX 0 85 SIZE 85 85 EQ_RGBA 96 96 96 255 TOLERANCE 1 1 1 0 +EXPECT framebuffer IDX 85 85 SIZE 85 85 EQ_RGBA 143 143 143 255 TOLERANCE 1 1 1 0 +EXPECT framebuffer IDX 171 85 SIZE 85 85 EQ_RGBA 191 191 191 255 TOLERANCE 1 1 1 0 -EXPECT framebuffer IDX 0 171 SIZE 85 85 EQ_RGBA 127 127 127 255 -EXPECT framebuffer IDX 85 171 SIZE 85 85 EQ_RGBA 191 191 191 255 +EXPECT framebuffer IDX 0 171 SIZE 85 85 EQ_RGBA 127 127 127 255 TOLERANCE 1 1 1 0 +EXPECT framebuffer IDX 85 171 SIZE 85 85 EQ_RGBA 191 191 191 255 TOLERANCE 1 1 1 0 EXPECT framebuffer IDX 171 171 SIZE 85 85 EQ_RGBA 255 255 255 255 diff --git a/tests/cases/minfilter.expect_fail.amber b/tests/cases/minfilter.expect_fail.amber new file mode 100644 index 000000000..7e8854ccf --- /dev/null +++ b/tests/cases/minfilter.expect_fail.amber @@ -0,0 +1,126 @@ +#!amber +# Copyright 2019 The Amber 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH + +SHADER fragment frag_shader_red GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +SHADER vertex vert_shader_tex GLSL +#version 430 +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoords_in; +layout(location = 0) out vec2 texcoords_out; +void main() { + gl_Position = position; + texcoords_out = texcoords_in; +} +END + +SHADER fragment frag_shader_tex GLSL +#version 430 +layout(location = 0) in vec2 texcoords_in; +layout(location = 0) out vec4 color_out; +uniform layout(set=0, binding=0) texture2D tex; +uniform layout(set=0, binding=1) sampler tex_sampler; +void main() { + color_out = texture(sampler2D(tex, tex_sampler), texcoords_in); +} +END + +BUFFER texture_small FORMAT B8G8R8A8_UNORM +BUFFER texture FORMAT B8G8R8A8_UNORM +BUFFER framebuffer_linear FORMAT B8G8R8A8_UNORM +BUFFER framebuffer_nearest FORMAT B8G8R8A8_UNORM +SAMPLER sampler_linear MIN_FILTER linear MAG_FILTER nearest +SAMPLER sampler_nearest MIN_FILTER nearest MAG_FILTER nearest +BUFFER position DATA_TYPE vec2 DATA +-1.0 -1.0 + 1.0 -1.0 + 1.0 1.0 +-1.0 1.0 +END +BUFFER texcoords DATA_TYPE vec2 DATA + 0.0 0.0 +16.0 0.0 +16.0 16.0 + 0.0 16.0 +END + +PIPELINE graphics pipeline_red + ATTACH vert_shader + ATTACH frag_shader_red + FRAMEBUFFER_SIZE 2 2 + BIND BUFFER texture_small AS color LOCATION 0 +END + +PIPELINE graphics pipeline_tex_repeat + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture_small AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler_nearest DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER texture AS color LOCATION 0 +END + +PIPELINE graphics pipeline_linear + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler_linear DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer_linear AS color LOCATION 0 +END + +PIPELINE graphics pipeline_nearest + ATTACH vert_shader_tex + ATTACH frag_shader_tex + BIND BUFFER texture AS sampled_image DESCRIPTOR_SET 0 BINDING 0 + BIND SAMPLER sampler_nearest DESCRIPTOR_SET 0 BINDING 1 + VERTEX_DATA position LOCATION 0 + VERTEX_DATA texcoords LOCATION 1 + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER framebuffer_nearest AS color LOCATION 0 +END + +# Generate 2x2 pattern. +CLEAR_COLOR pipeline_red 0 0 255 255 +CLEAR pipeline_red +RUN pipeline_red DRAW_RECT POS 0 0 SIZE 1 1 +RUN pipeline_red DRAW_RECT POS 1 1 SIZE 1 1 + +# Make the pattern repeat into 256x256 texture +RUN pipeline_tex_repeat DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 + +# Draw the repeated texture into a quad and again repeat 16 times +# to make it smaller. Do this with both nearest and linear filter mode. +CLEAR_COLOR pipeline_linear 0 255 0 255 +CLEAR pipeline_linear +RUN pipeline_linear DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 +CLEAR_COLOR pipeline_nearest 0 255 0 255 +CLEAR pipeline_nearest +RUN pipeline_nearest DRAW_ARRAY AS TRIANGLE_FAN START_IDX 0 COUNT 4 + +# Linear and nearest filtering produce different results so this should fail. +EXPECT framebuffer_nearest EQ_BUFFER framebuffer_linear