diff --git a/docs/amber_script.md b/docs/amber_script.md index de63afd0e..0b108de9c 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -119,6 +119,20 @@ FILL SERIES_FROM INC_BY ``` +#### Buffer Copy + +The COPY command copy all data, values and memory from to +. + +``` +COPY TO +``` + +Both buffers must be declared, and of the same type. + +Buffers used as copy destination can be used only as copy destination, and as +argument to an EXPECT command. + ### Pipelines #### Pipeline type diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index 0d4504025..4cafa96c3 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -76,6 +76,8 @@ Result Parser::Parse(const std::string& data) { r = ParseBuffer(); } else if (tok == "CLEAR") { r = ParseClear(); + } else if (tok == "COPY") { + r = ParseCopy(); } else if (tok == "EXPECT") { r = ParseExpect(); } else if (tok == "PIPELINE") { @@ -91,7 +93,7 @@ Result Parser::Parse(const std::string& data) { return Result(make_error(r.Error())); } - // Generate any needed color and depth attachments. This is done before + // Generate any needed color and depth attachments. This is done before // validating in case one of the pipelines specifies the framebuffer size // it needs to be verified against all other pipelines. for (const auto& pipeline : script_->GetPipelines()) { @@ -1178,5 +1180,60 @@ Result Parser::ParseExpect() { return ValidateEndOfStatement("EXPECT command"); } +Result Parser::ParseCopy() { + auto token = tokenizer_->NextToken(); + if (token->IsEOL() || token->IsEOS()) + return Result("missing buffer name after COPY"); + if (!token->IsString()) + return Result("invalid buffer name after COPY"); + + auto name = token->AsString(); + if (name == "TO") + return Result("missing buffer name between COPY and TO"); + + Buffer* buffer_from = script_->GetBuffer(name); + if (!buffer_from) + return Result("COPY origin buffer was not declared"); + + token = tokenizer_->NextToken(); + if (token->IsEOL() || token->IsEOS()) + return Result("missing 'TO' after COPY and buffer name"); + if (!token->IsString()) + return Result("expected 'TO' after COPY and buffer name"); + + name = token->AsString(); + if (name != "TO") + return Result("expected 'TO' after COPY and buffer name"); + + token = tokenizer_->NextToken(); + if (token->IsEOL() || token->IsEOS()) + return Result("missing buffer name after TO"); + if (!token->IsString()) + return Result("invalid buffer name after TO"); + + name = token->AsString(); + Buffer* buffer_to = script_->GetBuffer(name); + if (!buffer_to) + return Result("COPY destination buffer was not declared"); + + if (buffer_to->GetBufferType() == amber::BufferType::kUnknown) { + // Set destination buffer to mirror origin buffer + buffer_to->SetBufferType(buffer_from->GetBufferType()); + buffer_to->SetWidth(buffer_from->GetWidth()); + buffer_to->SetHeight(buffer_from->GetHeight()); + buffer_to->SetSize(buffer_from->GetSize()); + } + + if (buffer_from->GetBufferType() != buffer_to->GetBufferType()) + return Result("cannot COPY between buffers of different types"); + if (buffer_from == buffer_to) + return Result("COPY origin and destination buffers are identical"); + + auto cmd = MakeUnique(buffer_from, buffer_to); + script_->AddCommand(std::move(cmd)); + + return ValidateEndOfStatement("COPY command"); +} + } // namespace amberscript } // namespace amber diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 14f38bcc6..12fdb9011 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -64,6 +64,7 @@ class Parser : public amber::Parser { Result ParseRun(); Result ParseClear(); Result ParseExpect(); + Result ParseCopy(); // 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_test.cc b/src/amberscript/parser_test.cc index 5c7fc7fef..a5d1d8ece 100644 --- a/src/amberscript/parser_test.cc +++ b/src/amberscript/parser_test.cc @@ -4164,5 +4164,98 @@ EXPECT dest_buf IDX 0 EQ 22 ASSERT_TRUE(r.IsSuccess()) << r.Error(); } +TEST_F(AmberScriptParserTest, Copy) { + std::string in = R"( +BUFFER from FORMAT R32G32B32A32_SFLOAT +BUFFER dest FORMAT R32G32B32A32_SFLOAT +COPY from TO dest)"; + + 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->IsCopy()); +} + +TEST_F(AmberScriptParserTest, CopyUndeclaredOriginBuffer) { + std::string in = R"( +COPY from)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: COPY origin buffer was not declared", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyInvalidOriginBufferName) { + std::string in = R"( +COPY 123)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: invalid buffer name after COPY", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyUndeclaredDestinationBuffer) { + std::string in = R"( +BUFFER from FORMAT R32G32B32A32_SFLOAT +COPY from TO dest)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: COPY destination buffer was not declared", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyMissingOriginBuffer) { + std::string in = R"( +COPY)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: missing buffer name after COPY", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyMissingDestinationBuffer) { + std::string in = R"( +BUFFER from FORMAT R32G32B32A32_SFLOAT +COPY from TO)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: missing buffer name after TO", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyToSameBuffer) { + std::string in = R"( +BUFFER from FORMAT R32G32B32A32_SFLOAT +COPY from TO from)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: COPY origin and destination buffers are identical", r.Error()); +} + +TEST_F(AmberScriptParserTest, CopyMissingToKeyword) { + std::string in = R"( +BUFFER from FORMAT R32G32B32A32_SFLOAT +BUFFER dest FORMAT R32G32B32A32_SFLOAT +COPY from dest)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: expected 'TO' after COPY and buffer name", r.Error()); +} + } // namespace amberscript } // namespace amber diff --git a/src/buffer.cc b/src/buffer.cc index 48c35db1c..68b3b76ea 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -210,6 +210,17 @@ FormatBuffer* Buffer::AsFormatBuffer() { return static_cast(this); } +Result Buffer::CopyTo(Buffer* buffer) const { + if (buffer->width_ != width_) + return Result("Buffer::CopyBaseFields() buffers have a different width"); + if (buffer->height_ != height_) + return Result("Buffer::CopyBaseFields() buffers have a different height"); + if (buffer->size_ != size_) + return Result("Buffer::CopyBaseFields() buffers have a different size"); + buffer->values_ = values_; + return {}; +} + DataBuffer::DataBuffer() = default; DataBuffer::DataBuffer(BufferType type) : Buffer(type) {} diff --git a/src/buffer.h b/src/buffer.h index f5c5b04f8..d03a4a4e9 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -93,6 +93,9 @@ class Buffer { return reinterpret_cast(values_.data()); } + /// Copy the buffer values to an other one + Result CopyTo(Buffer* buffer) const; + protected: /// Create an un-typed buffer. Buffer(); diff --git a/src/command.cc b/src/command.cc index 7202fae98..7f42f3242 100644 --- a/src/command.cc +++ b/src/command.cc @@ -42,6 +42,10 @@ ComputeCommand* Command::AsCompute() { return static_cast(this); } +CopyCommand* Command::AsCopy() { + return static_cast(this); +} + DrawArraysCommand* Command::AsDrawArrays() { return static_cast(this); } @@ -108,6 +112,11 @@ BufferCommand::BufferCommand(BufferType type, Pipeline* pipeline) BufferCommand::~BufferCommand() = default; +CopyCommand::CopyCommand(Buffer* buffer_from, Buffer* buffer_to) + : Command(Type::kCopy), buffer_from_(buffer_from), buffer_to_(buffer_to) {} + +CopyCommand::~CopyCommand() = default; + ClearCommand::ClearCommand(Pipeline* pipeline) : PipelineCommand(Type::kClear, pipeline) {} diff --git a/src/command.h b/src/command.h index 02d77611b..cfbca11ec 100644 --- a/src/command.h +++ b/src/command.h @@ -34,6 +34,7 @@ class ClearColorCommand; class ClearDepthCommand; class ClearStencilCommand; class ComputeCommand; +class CopyCommand; class DrawArraysCommand; class DrawRectCommand; class EntryPointCommand; @@ -51,6 +52,7 @@ class Command { kClearDepth, kClearStencil, kCompute, + kCopy, kDrawArrays, kDrawRect, kEntryPoint, @@ -66,6 +68,7 @@ class Command { bool IsDrawRect() const { return command_type_ == Type::kDrawRect; } bool IsDrawArrays() const { return command_type_ == Type::kDrawArrays; } bool IsCompute() const { return command_type_ == Type::kCompute; } + bool IsCopy() const { return command_type_ == Type::kCopy; } bool IsProbe() const { return command_type_ == Type::kProbe; } bool IsProbeSSBO() const { return command_type_ == Type::kProbeSSBO; } bool IsBuffer() const { return command_type_ == Type::kBuffer; } @@ -83,6 +86,7 @@ class Command { ClearDepthCommand* AsClearDepth(); ClearStencilCommand* AsClearStencil(); ComputeCommand* AsCompute(); + CopyCommand* AsCopy(); DrawArraysCommand* AsDrawArrays(); DrawRectCommand* AsDrawRect(); EntryPointCommand* AsEntryPoint(); @@ -203,6 +207,19 @@ class ComputeCommand : public PipelineCommand { uint32_t z_ = 0; }; +class CopyCommand : public Command { + public: + CopyCommand(Buffer* buffer_from, Buffer* buffer_to); + ~CopyCommand() override; + + Buffer* GetBufferFrom() const { return buffer_from_; } + Buffer* GetBufferTo() const { return buffer_to_; } + + private: + Buffer* buffer_from_; + Buffer* buffer_to_; +}; + class Probe : public Command { public: struct Tolerance { diff --git a/src/executor.cc b/src/executor.cc index 8bcc31594..306380b43 100644 --- a/src/executor.cc +++ b/src/executor.cc @@ -97,6 +97,11 @@ Result Executor::Execute(Engine* engine, r = engine->DoClearDepth(cmd->AsClearDepth()); } else if (cmd->IsClearStencil()) { r = engine->DoClearStencil(cmd->AsClearStencil()); + } else if (cmd->IsCopy()) { + auto copy = cmd->AsCopy(); + auto buffer_from = copy->GetBufferFrom(); + auto buffer_to = copy->GetBufferTo(); + r = buffer_from->CopyTo(buffer_to); } else if (cmd->IsDrawRect()) { r = engine->DoDrawRect(cmd->AsDrawRect()); } else if (cmd->IsDrawArrays()) { diff --git a/tests/cases/copy_data_buffer.amber b/tests/cases/copy_data_buffer.amber new file mode 100644 index 000000000..cd41ce20f --- /dev/null +++ b/tests/cases/copy_data_buffer.amber @@ -0,0 +1,22 @@ +#!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. + +BUFFER orig_buf DATA_TYPE int32 SIZE 100 FILL 11 +BUFFER dest_buf DATA_TYPE int32 SIZE 100 FILL 22 + +EXPECT orig_buf IDX 0 EQ 11 +EXPECT dest_buf IDX 0 EQ 22 +COPY orig_buf TO dest_buf +EXPECT dest_buf IDX 0 EQ 11 diff --git a/tests/cases/copy_format_buffer.amber b/tests/cases/copy_format_buffer.amber new file mode 100644 index 000000000..466f50a52 --- /dev/null +++ b/tests/cases/copy_format_buffer.amber @@ -0,0 +1,38 @@ +#!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 GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() { + color_out = vec4(1.0, 0.0, 0.0, 1.0); +} +END + +BUFFER img_buf FORMAT B8G8R8A8_UNORM +BUFFER dest_buf FORMAT B8G8R8A8_UNORM + +PIPELINE graphics my_pipeline + ATTACH vert_shader + ATTACH frag_shader + FRAMEBUFFER_SIZE 256 256 + BIND BUFFER img_buf AS color LOCATION 0 +END + +RUN my_pipeline DRAW_RECT POS 0 0 SIZE 256 256 +EXPECT img_buf IDX 0 0 SIZE 256 256 EQ_RGBA 1 0 0 1 +COPY img_buf TO dest_buf +EXPECT dest_buf IDX 0 0 SIZE 256 256 EQ_RGBA 1 0 0 1