aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Thomson <paulthomson@google.com>2021-10-12 13:38:21 +0100
committerPaul Thomson <paulthomson@google.com>2021-10-12 13:38:42 +0100
commitf3b1d8fd9a3dfb99df5b8ba7900b3493ee0329a8 (patch)
tree17024a345cc88eba5ba0a838d8fd11af3aef8350
parent69207cd4d09b81e2b6d350d8a7a929904e4aa66f (diff)
parent8797ee109e7a6ea4d1f58f387f757545fa35325b (diff)
downloadamber-f3b1d8fd9a3dfb99df5b8ba7900b3493ee0329a8.tar.gz
Merge commit '8797ee109e7a6ea4d1f58f387f757545fa35325b' into master
Bug: b/201652781 Change-Id: Ibb8fbe9137d18fa6f6109ad5f51ec8e7c5f9cfbb
-rw-r--r--docs/amber_script.md28
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/amberscript/parser.cc123
-rw-r--r--src/amberscript/parser.h1
-rw-r--r--src/amberscript/parser_bind_test.cc257
-rw-r--r--src/amberscript/parser_viewport_test.cc388
-rw-r--r--src/command.h12
-rw-r--r--src/float16_helper.cc22
-rw-r--r--src/pipeline.cc8
-rw-r--r--src/pipeline.h9
-rw-r--r--src/pipeline_data.h20
-rw-r--r--src/pipeline_test.cc10
-rw-r--r--src/vkscript/command_parser.cc6
-rw-r--r--src/vulkan/buffer_backed_descriptor.cc42
-rw-r--r--src/vulkan/buffer_backed_descriptor.h6
-rw-r--r--src/vulkan/buffer_descriptor.cc76
-rw-r--r--src/vulkan/buffer_descriptor.h22
-rw-r--r--src/vulkan/descriptor.h2
-rw-r--r--src/vulkan/engine_vulkan.cc2
-rw-r--r--src/vulkan/graphics_pipeline.cc10
-rw-r--r--src/vulkan/image_descriptor.cc64
-rw-r--r--src/vulkan/image_descriptor.h6
-rw-r--r--src/vulkan/pipeline.cc13
-rw-r--r--src/vulkan/pipeline_test.cc4
-rw-r--r--tests/cases/compute_descriptor_array_ssbo.amber21
-rw-r--r--tests/cases/compute_descriptor_array_storagetexelbuffer.amber23
-rw-r--r--tests/cases/compute_dynamic_buffers.amber9
-rw-r--r--tests/cases/compute_dynamic_buffers_descriptor_array.amber17
-rw-r--r--tests/cases/graphics_descriptor_array_combined_image_sampler.amber13
-rw-r--r--tests/cases/graphics_descriptor_array_sampled_image.amber14
-rw-r--r--tests/cases/non_default_entry_point.amber2
-rw-r--r--tests/cases/shader_specialization.amber2
-rw-r--r--tests/cases/storage16.amber1
-rw-r--r--tests/cases/viewport.amber59
-rw-r--r--tests/cases/viewport_depth.amber61
35 files changed, 1237 insertions, 117 deletions
diff --git a/docs/amber_script.md b/docs/amber_script.md
index 57e0632..295cdc7 100644
--- a/docs/amber_script.md
+++ b/docs/amber_script.md
@@ -485,6 +485,12 @@ The following commands are all specified within the `PIPELINE` command.
```
```groovy
+ # Set the viewport size. If no viewport is provided then it defaults to the
+ # whole framebuffer size. Depth range defaults to 0 to 1.
+ VIEWPORT {x} {y} SIZE {width} {height} [MIN_DEPTH {mind}] [MAX_DEPTH {maxd}]
+```
+
+```groovy
# Set subgroup size control setting. Require that subgroups must be launched
# with all invocations active for given shader. Allow SubgroupSize to vary
# for given shader. Require a specific SubgroupSize the for given shader.
@@ -494,7 +500,7 @@ The following commands are all specified within the `PIPELINE` command.
# and be less than or equal to maxSubgroupSize
# - MIN to set the required subgroup size to the minSubgroupSize
# - MAX to set the required subgroup size to the maxSubgroupSize
- SUBROUP {name_of_shader}
+ SUBGROUP {name_of_shader}
FULLY_POPULATED {fully_populated_enable}
VARYING_SIZE {varying_size_enable}
REQUIRED_SIZE {subgroup_size}
@@ -559,13 +565,18 @@ All BIND BUFFER and BIND SAMPLER commands below define a descriptor set and bind
These commands can be replaced with BIND BUFFER_ARRAY and BIND SAMPLER_ARRAY commands.
In these cases multiple buffer or sampler names need to be provided, separated by spaces.
This creates a descriptor array of buffers or samplers bound to the same descriptor set
-and binding ID. An array of offsets should be provided via `OFFSET offset1 offset2 ...`
-when using dynamic buffers with BUFFER_ARRAY.
+and binding ID. An array of dynamic offsets should be provided via `OFFSET offset1 offset2 ...`
+when using dynamic buffers with BUFFER_ARRAY. Optional descriptor binding offset(s) and range(s)
+can be defined via `DESCRIPTOR_OFFSET offset1 offset2 ...` and
+`DESCRIPTOR_RANGE range1 range2 ...` when using uniform or storage buffers. Offsets and
+ranges can be used also with dynamic buffers.
```groovy
# Bind the buffer of the given |buffer_type| at the given descriptor set
- # and binding. The buffer will use a start index of 0.
+ # and binding. The buffer will use a byte offset |descriptor_offset|
+ # with range |range|.
BIND {BUFFER | BUFFER_ARRAY} {buffer_name} AS {buffer_type} DESCRIPTOR_SET _id_ \
- BINDING _id_
+ BINDING _id_ [ DESCRIPTOR_OFFSET _descriptor_offset_ (default 0) ] \
+ [ DESCRIPTOR_RANGE _range_ (default -1 == VK_WHOLE_SIZE) ]
# Attach |buffer_name| as a storage image. The MIP level will have a base
# value of |level|.
@@ -586,9 +597,12 @@ when using dynamic buffers with BUFFER_ARRAY.
BIND {SAMPLER | SAMPLER_ARRAY} {sampler_name} DESCRIPTOR_SET _id_ BINDING _id_
# Bind |buffer_name| as dynamic uniform/storage buffer at the given descriptor set
- # and binding. The buffer will use a start index of |offset|.
+ # and binding. The buffer will use a byte offset |offset| + |descriptor_offset|
+ # with range |range|.
BIND {BUFFER | BUFFER_ARRAY} {buffer_name} AS {uniform_dynamic | storage_dynamic} \
- DESCRIPTOR_SET _id_ BINDING _id_ OFFSET _offset_
+ DESCRIPTOR_SET _id_ BINDING _id_ OFFSET _offset_ \
+ [ DESCRIPTOR_OFFSET _descriptor_offset_ (default 0) ] \
+ [ DESCRIPTOR_RANGE _range_ (default -1 == VK_WHOLE_SIZE) ]
```
```groovy
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 00e3e54..901681f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -159,6 +159,7 @@ if (${AMBER_ENABLE_TESTS})
amberscript/parser_struct_test.cc
amberscript/parser_subgroup_size_control_test.cc
amberscript/parser_test.cc
+ amberscript/parser_viewport_test.cc
buffer_test.cc
command_data_test.cc
descriptor_set_and_binding_parser_test.cc
diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc
index 50760f0..809baa1 100644
--- a/src/amberscript/parser.cc
+++ b/src/amberscript/parser.cc
@@ -602,6 +602,8 @@ Result Parser::ParsePipelineBody(const std::string& cmd_name,
r = ParsePipelineShaderOptimizations(pipeline.get());
} else if (tok == "FRAMEBUFFER_SIZE") {
r = ParsePipelineFramebufferSize(pipeline.get());
+ } else if (tok == "VIEWPORT") {
+ r = ParsePipelineViewport(pipeline.get());
} else if (tok == "BIND") {
r = ParsePipelineBind(pipeline.get());
} else if (tok == "VERTEX_DATA") {
@@ -949,6 +951,75 @@ Result Parser::ParsePipelineFramebufferSize(Pipeline* pipeline) {
return ValidateEndOfStatement("FRAMEBUFFER_SIZE command");
}
+Result Parser::ParsePipelineViewport(Pipeline* pipeline) {
+ Viewport vp;
+ vp.mind = 0.0f;
+ vp.maxd = 1.0f;
+
+ float val[2];
+ for (int i = 0; i < 2; i++) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("missing offset for VIEWPORT command");
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return Result("invalid offset for VIEWPORT command");
+
+ val[i] = token->AsFloat();
+ }
+ vp.x = val[0];
+ vp.y = val[1];
+
+ auto token = tokenizer_->NextToken();
+ if (!token->IsIdentifier() || token->AsString() != "SIZE")
+ return Result("missing SIZE for VIEWPORT command");
+
+ for (int i = 0; i < 2; i++) {
+ token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("missing size for VIEWPORT command");
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return Result("invalid size for VIEWPORT command");
+
+ val[i] = token->AsFloat();
+ }
+ vp.w = val[0];
+ vp.h = val[1];
+
+ token = tokenizer_->PeekNextToken();
+ while (token->IsIdentifier()) {
+ if (token->AsString() == "MIN_DEPTH") {
+ tokenizer_->NextToken();
+ token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("missing min_depth for VIEWPORT command");
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return Result("invalid min_depth for VIEWPORT command");
+
+ vp.mind = token->AsFloat();
+ }
+ if (token->AsString() == "MAX_DEPTH") {
+ tokenizer_->NextToken();
+ token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("missing max_depth for VIEWPORT command");
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return Result("invalid max_depth for VIEWPORT command");
+
+ vp.maxd = token->AsFloat();
+ }
+
+ token = tokenizer_->PeekNextToken();
+ }
+
+ pipeline->GetPipelineData()->SetViewport(vp);
+
+ return ValidateEndOfStatement("VIEWPORT command");
+}
+
Result Parser::ToBufferType(const std::string& name, BufferType* type) {
assert(type);
if (name == "color")
@@ -1172,10 +1243,60 @@ Result Parser::ParsePipelineBind(Pipeline* pipeline) {
}
}
+ // Set default descriptor buffer offsets to 0 and descriptor buffer
+ // ranges to VK_WHOLE_SIZE (~0ULL).
+ std::vector<uint64_t> descriptor_offsets(buffers.size(), 0);
+ std::vector<uint64_t> descriptor_ranges(buffers.size(), ~0ULL);
+ if (buffer_type == BufferType::kUniformDynamic ||
+ buffer_type == BufferType::kStorageDynamic ||
+ buffer_type == BufferType::kStorage ||
+ buffer_type == BufferType::kUniform) {
+ token = tokenizer_->PeekNextToken();
+ if (token->IsIdentifier() &&
+ token->AsString() == "DESCRIPTOR_OFFSET") {
+ token = tokenizer_->NextToken();
+ for (size_t i = 0; i < buffers.size(); i++) {
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger()) {
+ if (i > 0) {
+ return Result(
+ "expecting a DESCRIPTOR_OFFSET value for each buffer in "
+ "the array");
+ } else {
+ return Result(
+ "expecting an integer value for DESCRIPTOR_OFFSET");
+ }
+ }
+ descriptor_offsets[i] = token->AsUint64();
+ }
+ }
+
+ token = tokenizer_->PeekNextToken();
+ if (token->IsIdentifier() &&
+ token->AsString() == "DESCRIPTOR_RANGE") {
+ token = tokenizer_->NextToken();
+ for (size_t i = 0; i < buffers.size(); i++) {
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger()) {
+ if (i > 0) {
+ return Result(
+ "expecting a DESCRIPTOR_RANGE value for each buffer in "
+ "the array");
+ } else {
+ return Result(
+ "expecting an integer value for DESCRIPTOR_RANGE");
+ }
+ }
+ descriptor_ranges[i] = token->AsUint64();
+ }
+ }
+ }
+
pipeline->ClearBuffers(descriptor_set, binding);
for (size_t i = 0; i < buffers.size(); i++) {
pipeline->AddBuffer(buffers[i], buffer_type, descriptor_set, binding,
- base_mip_level, dynamic_offsets[i]);
+ base_mip_level, dynamic_offsets[i],
+ descriptor_offsets[i], descriptor_ranges[i]);
}
} else if (token->IsIdentifier() && token->AsString() == "KERNEL") {
token = tokenizer_->NextToken();
diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h
index e8cb835..6fb25ce 100644
--- a/src/amberscript/parser.h
+++ b/src/amberscript/parser.h
@@ -65,6 +65,7 @@ class Parser : public amber::Parser {
Result ParsePipelineShaderCompileOptions(Pipeline*);
Result ParsePipelineSubgroup(Pipeline* pipeline);
Result ParsePipelineFramebufferSize(Pipeline*);
+ Result ParsePipelineViewport(Pipeline*);
Result ParsePipelineBind(Pipeline*);
Result ParsePipelineVertexData(Pipeline*);
Result ParsePipelineIndexData(Pipeline*);
diff --git a/src/amberscript/parser_bind_test.cc b/src/amberscript/parser_bind_test.cc
index 64f3948..a39b69e 100644
--- a/src/amberscript/parser_bind_test.cc
+++ b/src/amberscript/parser_bind_test.cc
@@ -1393,11 +1393,53 @@ END
EXPECT_EQ(BufferType::kUniform, bufs[0].type);
EXPECT_EQ(1U, bufs[0].descriptor_set);
EXPECT_EQ(2U, bufs[0].binding);
+ EXPECT_EQ(0U, bufs[0].descriptor_offset);
+ EXPECT_EQ(~0ULL, bufs[0].descriptor_range);
EXPECT_EQ(static_cast<uint32_t>(0), bufs[0].location);
EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
bufs[0].buffer->GetFormat()->GetFormatType());
}
+TEST_F(AmberScriptParserTest, BindBufferDescriptorOffsetAndRange) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_OFFSET 256 DESCRIPTOR_RANGE 512
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 3 DESCRIPTOR_OFFSET 256 DESCRIPTOR_RANGE -1
+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(2U, bufs.size());
+ EXPECT_EQ(BufferType::kUniform, bufs[0].type);
+ EXPECT_EQ(1U, bufs[0].descriptor_set);
+ EXPECT_EQ(2U, bufs[0].binding);
+ EXPECT_EQ(256U, bufs[0].descriptor_offset);
+ EXPECT_EQ(512U, bufs[0].descriptor_range);
+ EXPECT_EQ(static_cast<uint32_t>(0), bufs[0].location);
+ EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
+ bufs[0].buffer->GetFormat()->GetFormatType());
+ // Verify the descriptor range from the second buffer.
+ EXPECT_EQ(~0ULL, bufs[1].descriptor_range);
+}
+
TEST_F(AmberScriptParserTest, BindBufferMissingBindingValue) {
std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
@@ -2859,6 +2901,33 @@ END)";
EXPECT_EQ("13: missing BINDING for BIND command", r.Error());
}
+TEST_F(AmberScriptParserTest, BindDescriptorOffsetWithUnsupportedBufferType) {
+ std::string unsupported_buffer_types[] = {"uniform_texel_buffer",
+ "storage_texel_buffer",
+ "sampled_image", "storage_image"};
+
+ for (const auto& buffer_type : unsupported_buffer_types) {
+ std::ostringstream in;
+ in << R"(
+SHADER compute compute_shader GLSL
+# GLSL Shader
+END
+
+BUFFER texture FORMAT R8G8B8A8_UNORM
+
+PIPELINE compute pipeline
+ ATTACH compute_shader
+ BIND BUFFER texture AS )"
+ << buffer_type << R"( DESCRIPTOR_SET 0 BINDING 0 DESCRIPTOR_OFFSET 0
+END)";
+ Parser parser;
+ Result r = parser.Parse(in.str());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("10: extra parameters after BIND command: DESCRIPTOR_OFFSET",
+ r.Error());
+ }
+}
+
TEST_F(AmberScriptParserTest, BindBufferArray) {
std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
@@ -2891,12 +2960,200 @@ END
EXPECT_EQ(BufferType::kUniform, bufs[i].type);
EXPECT_EQ(1U, bufs[i].descriptor_set);
EXPECT_EQ(2U, bufs[i].binding);
+ EXPECT_EQ(0U, bufs[i].descriptor_offset);
+ EXPECT_EQ(~0ULL, bufs[i].descriptor_range);
+ EXPECT_EQ(static_cast<uint32_t>(0), bufs[i].location);
+ EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
+ bufs[i].buffer->GetFormat()->GetFormatType());
+ }
+}
+
+TEST_F(AmberScriptParserTest, BindBufferArrayWithDescriptorOffsetAndRange) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_buf1 FORMAT R32G32B32A32_SFLOAT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ BIND BUFFER_ARRAY my_buf1 my_buf1 AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_OFFSET 256 512 DESCRIPTOR_RANGE 1024 2048
+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(2U, bufs.size());
+ for (size_t i = 0; i < 2; i++) {
+ EXPECT_EQ(BufferType::kUniform, bufs[i].type);
+ EXPECT_EQ(1U, bufs[i].descriptor_set);
+ EXPECT_EQ(2U, bufs[i].binding);
+ EXPECT_EQ(256U * (i + 1), bufs[i].descriptor_offset);
+ EXPECT_EQ(1024U * (i + 1), bufs[i].descriptor_range);
EXPECT_EQ(static_cast<uint32_t>(0), bufs[i].location);
EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
bufs[i].buffer->GetFormat()->GetFormatType());
}
}
+TEST_F(AmberScriptParserTest,
+ BindDynamicBufferArrayWithDescriptorOffsetAndRange) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_buf1 FORMAT R32G32B32A32_SFLOAT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ BIND BUFFER_ARRAY my_buf1 my_buf1 AS uniform_dynamic DESCRIPTOR_SET 1 BINDING 2 OFFSET 16 32 DESCRIPTOR_OFFSET 256 512 DESCRIPTOR_RANGE 1024 2048
+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(2U, bufs.size());
+ for (size_t i = 0; i < 2; i++) {
+ EXPECT_EQ(BufferType::kUniformDynamic, bufs[i].type);
+ EXPECT_EQ(1U, bufs[i].descriptor_set);
+ EXPECT_EQ(2U, bufs[i].binding);
+ EXPECT_EQ(16U * (i + 1), bufs[i].dynamic_offset);
+ EXPECT_EQ(256U * (i + 1), bufs[i].descriptor_offset);
+ EXPECT_EQ(1024U * (i + 1), bufs[i].descriptor_range);
+ EXPECT_EQ(static_cast<uint32_t>(0), bufs[i].location);
+ EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
+ bufs[i].buffer->GetFormat()->GetFormatType());
+ }
+}
+
+TEST_F(AmberScriptParserTest, BindBufferArrayOnlyOneDescriptorOffset) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ BIND BUFFER_ARRAY my_buf my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_OFFSET 256
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(
+ "13: expecting a DESCRIPTOR_OFFSET value for each buffer in the array",
+ r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BindBufferArrayOnlyOneDescriptorRange) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ BIND BUFFER_ARRAY my_buf my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_RANGE 256
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(
+ "13: expecting a DESCRIPTOR_RANGE value for each buffer in the array",
+ r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BindUniformBufferEmptyDescriptorOffset) {
+ std::string in = R"(
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+PIPELINE graphics my_pipeline
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_OFFSET
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: expecting an integer value for DESCRIPTOR_OFFSET", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BindUniformBufferInvalidDescriptorOffset) {
+ std::string in = R"(
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+PIPELINE graphics my_pipeline
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_OFFSET foo
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: expecting an integer value for DESCRIPTOR_OFFSET", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BindUniformBufferEmptyDescriptorRange) {
+ std::string in = R"(
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+PIPELINE graphics my_pipeline
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_RANGE
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: expecting an integer value for DESCRIPTOR_RANGE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BindUniformBufferInvalidDescriptorRange) {
+ std::string in = R"(
+BUFFER my_buf FORMAT R32G32B32A32_SFLOAT
+PIPELINE graphics my_pipeline
+ BIND BUFFER my_buf AS uniform DESCRIPTOR_SET 1 BINDING 2 DESCRIPTOR_RANGE foo
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: expecting an integer value for DESCRIPTOR_RANGE", r.Error());
+}
+
TEST_F(AmberScriptParserTest, BindBufferArrayOnlyOneBuffer) {
std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
diff --git a/src/amberscript/parser_viewport_test.cc b/src/amberscript/parser_viewport_test.cc
new file mode 100644
index 0000000..ec19fb2
--- /dev/null
+++ b/src/amberscript/parser_viewport_test.cc
@@ -0,0 +1,388 @@
+// Copyright 2021 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, NoViewport) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_FALSE(pipeline->GetPipelineData()->HasViewportData());
+}
+
+TEST_F(AmberScriptParserTest, ViewportNoDepth) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 5.0 7.0 SIZE 10.0 12.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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(5.0f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(7.0f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(10.0f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(12.0f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(0.0f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(1.0f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportMinDepth) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 12.2 9.7 SIZE 0.5 106.1 MIN_DEPTH 0.3
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(12.2f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(9.7f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(0.5f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(106.1f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(0.3f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(1.0f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportMaxDepth) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 12.2 9.7 SIZE 0.5 106.1 MAX_DEPTH 0.456
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(12.2f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(9.7f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(0.5f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(106.1f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(0.0f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(0.456f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportAllValues) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT -0.6 5.2 SIZE 13.8 9.4 MIN_DEPTH 0.5 MAX_DEPTH 0.6
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(-0.6f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(5.2f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(13.8f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(9.4f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(0.5f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(0.6f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportIntegers) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT -2 7 SIZE 15 20 MIN_DEPTH 1 MAX_DEPTH 2
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(-2.0f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(7.0f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(15.0f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(20.0f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(1.0f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(2.0f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportMixedIntegers) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT -2 13.1 SIZE 15.9 20
+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());
+
+ auto* pipeline = pipelines[0].get();
+ ASSERT_TRUE(pipeline->GetPipelineData()->HasViewportData());
+ ASSERT_FLOAT_EQ(-2.0f, pipeline->GetPipelineData()->GetViewport().x);
+ ASSERT_FLOAT_EQ(13.1f, pipeline->GetPipelineData()->GetViewport().y);
+ ASSERT_FLOAT_EQ(15.9f, pipeline->GetPipelineData()->GetViewport().w);
+ ASSERT_FLOAT_EQ(20.0f, pipeline->GetPipelineData()->GetViewport().h);
+ ASSERT_FLOAT_EQ(0.0f, pipeline->GetPipelineData()->GetViewport().mind);
+ ASSERT_FLOAT_EQ(1.0f, pipeline->GetPipelineData()->GetViewport().maxd);
+}
+
+TEST_F(AmberScriptParserTest, ViewportInvalidMissingSize) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 0.0 2.0 12.0 24.0
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("15: missing SIZE for VIEWPORT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ViewportInvalidSizeNotOptional) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 0.0 2.0
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("16: missing SIZE for VIEWPORT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ViewportInvalidMissingOffset) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 0.0 SIZE 12.0 24.0
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("15: invalid offset for VIEWPORT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ViewportInvalidMissingSizeValue) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 0.0 2.0 SIZE 12.0
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("16: missing size for VIEWPORT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ViewportInvalidMissingDepthValue) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+BUFFER my_fb FORMAT R32G32B32A32_SFLOAT
+BUFFER my_ds FORMAT D32_SFLOAT_S8_UINT
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+ BIND BUFFER my_fb AS color LOCATION 0
+ BIND BUFFER my_ds AS depth_stencil
+
+ VIEWPORT 0.0 2.0 SIZE 12.0 24.0 MIN_DEPTH MAX_DEPTH 1.0
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("15: invalid min_depth for VIEWPORT command", r.Error());
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/command.h b/src/command.h
index 207c336..26b9dc2 100644
--- a/src/command.h
+++ b/src/command.h
@@ -546,6 +546,16 @@ class BufferCommand : public BindableResourceCommand {
}
uint32_t GetDynamicOffset() const { return dynamic_offset_; }
+ void SetDescriptorOffset(uint64_t descriptor_offset) {
+ descriptor_offset_ = descriptor_offset;
+ }
+ uint64_t GetDescriptorOffset() const { return descriptor_offset_; }
+
+ void SetDescriptorRange(uint64_t descriptor_range) {
+ descriptor_range_ = descriptor_range;
+ }
+ uint64_t GetDescriptorRange() const { return descriptor_range_; }
+
void SetValues(std::vector<Value>&& values) { values_ = std::move(values); }
const std::vector<Value>& GetValues() const { return values_; }
@@ -565,6 +575,8 @@ class BufferCommand : public BindableResourceCommand {
uint32_t offset_ = 0;
uint32_t base_mip_level_ = 0;
uint32_t dynamic_offset_ = 0;
+ uint64_t descriptor_offset_ = 0;
+ uint64_t descriptor_range_ = ~0ULL;
std::vector<Value> values_;
};
diff --git a/src/float16_helper.cc b/src/float16_helper.cc
index 617bd72..5cb35e7 100644
--- a/src/float16_helper.cc
+++ b/src/float16_helper.cc
@@ -15,6 +15,7 @@
#include "src/float16_helper.h"
#include <cassert>
+#include <cstring>
// Float10
// | 9 8 7 6 5 | 4 3 2 1 0 |
@@ -75,8 +76,11 @@ float HexFloat16ToFloat(const uint8_t* value) {
}
uint32_t hex = sign | exponent | mantissa;
- float* hex_float = reinterpret_cast<float*>(&hex);
- return *hex_float;
+ float hex_float;
+ static_assert((sizeof(uint32_t) == sizeof(float)),
+ "sizeof(uint32_t) != sizeof(float)");
+ memcpy(&hex_float, &hex, sizeof(float));
+ return hex_float;
}
// Convert float |value| whose size is 11 bits to 32 bits float
@@ -89,8 +93,11 @@ float HexFloat11ToFloat(const uint8_t* value) {
uint32_t mantissa = (static_cast<uint32_t>(value[0]) & 0x3f) << 17U;
uint32_t hex = exponent | mantissa;
- float* hex_float = reinterpret_cast<float*>(&hex);
- return *hex_float;
+ float hex_float;
+ static_assert((sizeof(uint32_t) == sizeof(float)),
+ "sizeof(uint32_t) != sizeof(float)");
+ memcpy(&hex_float, &hex, sizeof(float));
+ return hex_float;
}
// Convert float |value| whose size is 10 bits to 32 bits float
@@ -103,8 +110,11 @@ float HexFloat10ToFloat(const uint8_t* value) {
uint32_t mantissa = (static_cast<uint32_t>(value[0]) & 0x1f) << 18U;
uint32_t hex = exponent | mantissa;
- float* hex_float = reinterpret_cast<float*>(&hex);
- return *hex_float;
+ float hex_float;
+ static_assert((sizeof(uint32_t) == sizeof(float)),
+ "sizeof(uint32_t) != sizeof(float)");
+ memcpy(&hex_float, &hex, sizeof(float));
+ return hex_float;
}
} // namespace
diff --git a/src/pipeline.cc b/src/pipeline.cc
index 55c6bae..9b73041 100644
--- a/src/pipeline.cc
+++ b/src/pipeline.cc
@@ -516,7 +516,9 @@ void Pipeline::AddBuffer(Buffer* buf,
uint32_t descriptor_set,
uint32_t binding,
uint32_t base_mip_level,
- uint32_t dynamic_offset) {
+ uint32_t dynamic_offset,
+ uint64_t descriptor_offset,
+ uint64_t descriptor_range) {
buffers_.push_back(BufferInfo{buf});
auto& info = buffers_.back();
@@ -526,6 +528,8 @@ void Pipeline::AddBuffer(Buffer* buf,
info.base_mip_level = base_mip_level;
info.dynamic_offset = dynamic_offset;
info.sampler = buf->GetSampler();
+ info.descriptor_offset = descriptor_offset;
+ info.descriptor_range = descriptor_range;
}
void Pipeline::AddBuffer(Buffer* buf,
@@ -862,7 +866,7 @@ Result Pipeline::GenerateOpenCLPodBuffers() {
opencl_pod_buffer_map_.insert(
buf_iter,
std::make_pair(std::make_pair(descriptor_set, binding), buffer));
- AddBuffer(buffer, buffer_type, descriptor_set, binding, 0, 0);
+ AddBuffer(buffer, buffer_type, descriptor_set, binding, 0, 0, 0, ~0ULL);
} else {
buffer = buf_iter->second;
}
diff --git a/src/pipeline.h b/src/pipeline.h
index ec38a3a..7a80ad4 100644
--- a/src/pipeline.h
+++ b/src/pipeline.h
@@ -203,6 +203,8 @@ class Pipeline {
uint32_t offset = 0;
uint32_t stride = 0;
Sampler* sampler = nullptr;
+ uint64_t descriptor_offset = 0;
+ uint64_t descriptor_range = ~0ULL; // ~0ULL == VK_WHOLE_SIZE
};
/// Information on a sampler attached to the pipeline.
@@ -335,13 +337,16 @@ class Pipeline {
Buffer* GetIndexBuffer() const { return index_buffer_; }
/// Adds |buf| of |type| to the pipeline at the given |descriptor_set|,
- /// |binding|, |base_mip_level|, and |dynamic_offset|.
+ /// |binding|, |base_mip_level|, |descriptor_offset|, |descriptor_range| and
+ /// |dynamic_offset|.
void AddBuffer(Buffer* buf,
BufferType type,
uint32_t descriptor_set,
uint32_t binding,
uint32_t base_mip_level,
- uint32_t dynamic_offset);
+ uint32_t dynamic_offset,
+ uint64_t descriptor_offset,
+ uint64_t descriptor_range);
/// Adds |buf| to the pipeline at the given |arg_name|.
void AddBuffer(Buffer* buf, BufferType type, const std::string& arg_name);
/// Adds |buf| to the pipeline at the given |arg_no|.
diff --git a/src/pipeline_data.h b/src/pipeline_data.h
index dc67c03..9a3b4d1 100644
--- a/src/pipeline_data.h
+++ b/src/pipeline_data.h
@@ -21,6 +21,15 @@
namespace amber {
+struct Viewport {
+ float x;
+ float y;
+ float w;
+ float h;
+ float mind;
+ float maxd;
+};
+
/// Stores information used to configure a pipeline.
class PipelineData {
public:
@@ -161,6 +170,14 @@ class PipelineData {
void SetAlphaBlendOp(BlendOp op) { alpha_blend_op_ = op; }
BlendOp GetAlphaBlendOp() const { return alpha_blend_op_; }
+ void SetViewport(const Viewport& v) {
+ has_viewport_data = true;
+ vp = v;
+ }
+
+ bool HasViewportData() const { return has_viewport_data; }
+ const Viewport& GetViewport() const { return vp; }
+
private:
StencilOp front_fail_op_ = StencilOp::kKeep;
StencilOp front_pass_op_ = StencilOp::kKeep;
@@ -213,6 +230,9 @@ class PipelineData {
float depth_bias_slope_factor_ = 0.0f;
float min_depth_bounds_ = 0.0f;
float max_depth_bounds_ = 0.0f;
+
+ bool has_viewport_data = false;
+ Viewport vp;
};
} // namespace amber
diff --git a/src/pipeline_test.cc b/src/pipeline_test.cc
index e16313c..c0d2f67 100644
--- a/src/pipeline_test.cc
+++ b/src/pipeline_test.cc
@@ -262,7 +262,7 @@ TEST_F(PipelineTest, PipelineBufferWithoutFormat) {
auto buf = MakeUnique<Buffer>();
buf->SetName("MyBuffer");
- p.AddBuffer(buf.get(), BufferType::kStorage, 0, 0, 0, 0);
+ p.AddBuffer(buf.get(), BufferType::kStorage, 0, 0, 0, 0, 0, 0);
Result r = p.Validate();
EXPECT_FALSE(r.IsSuccess()) << r.Error();
@@ -365,11 +365,11 @@ TEST_F(PipelineTest, Clone) {
auto buf1 = MakeUnique<Buffer>();
buf1->SetName("buf1");
- p.AddBuffer(buf1.get(), BufferType::kStorage, 1, 1, 0, 0);
+ p.AddBuffer(buf1.get(), BufferType::kStorage, 1, 1, 0, 0, 0, 0);
auto buf2 = MakeUnique<Buffer>();
buf2->SetName("buf2");
- p.AddBuffer(buf2.get(), BufferType::kStorage, 1, 2, 0, 16);
+ p.AddBuffer(buf2.get(), BufferType::kStorage, 1, 2, 0, 16, 256, 512);
auto clone = p.Clone();
EXPECT_EQ("", clone->GetName());
@@ -400,11 +400,15 @@ TEST_F(PipelineTest, Clone) {
EXPECT_EQ(1U, bufs[0].descriptor_set);
EXPECT_EQ(1U, bufs[0].binding);
EXPECT_EQ(0U, bufs[0].dynamic_offset);
+ EXPECT_EQ(0U, bufs[0].descriptor_offset);
+ EXPECT_EQ(0U, bufs[0].descriptor_range);
EXPECT_EQ("buf2", bufs[1].buffer->GetName());
EXPECT_EQ(1U, bufs[1].descriptor_set);
EXPECT_EQ(2U, bufs[1].binding);
EXPECT_EQ(16U, bufs[1].dynamic_offset);
+ EXPECT_EQ(256U, bufs[1].descriptor_offset);
+ EXPECT_EQ(512U, bufs[1].descriptor_range);
}
TEST_F(PipelineTest, OpenCLUpdateBindings) {
diff --git a/src/vkscript/command_parser.cc b/src/vkscript/command_parser.cc
index 0953344..610c7e6 100644
--- a/src/vkscript/command_parser.cc
+++ b/src/vkscript/command_parser.cc
@@ -577,7 +577,8 @@ Result CommandParser::ProcessSSBO() {
buffer = b.get();
script_->AddBuffer(std::move(b));
pipeline_->ClearBuffers(set, binding);
- pipeline_->AddBuffer(buffer, BufferType::kStorage, set, binding, 0, 0);
+ pipeline_->AddBuffer(buffer, BufferType::kStorage, set, binding, 0, 0, 0,
+ ~0ULL);
}
cmd->SetBuffer(buffer);
}
@@ -729,7 +730,8 @@ Result CommandParser::ProcessUniform() {
buffer = b.get();
script_->AddBuffer(std::move(b));
pipeline_->ClearBuffers(set, binding);
- pipeline_->AddBuffer(buffer, BufferType::kUniform, set, binding, 0, 0);
+ pipeline_->AddBuffer(buffer, BufferType::kUniform, set, binding, 0, 0, 0,
+ ~0ULL);
}
cmd->SetBuffer(buffer);
diff --git a/src/vulkan/buffer_backed_descriptor.cc b/src/vulkan/buffer_backed_descriptor.cc
index 2f7aa98..d1b1aed 100644
--- a/src/vulkan/buffer_backed_descriptor.cc
+++ b/src/vulkan/buffer_backed_descriptor.cc
@@ -35,26 +35,18 @@ BufferBackedDescriptor::~BufferBackedDescriptor() = default;
Result BufferBackedDescriptor::RecordCopyDataToResourceIfNeeded(
CommandBuffer* command) {
- auto resources = GetResources();
- auto buffers = GetAmberBuffers();
- if (resources.size() != buffers.size())
- return Result(
- "Vulkan: BufferBackedDescriptor::RecordCopyDataToResourceIfNeeded() "
- "resource and buffer vector sizes are not matching");
-
- for (size_t i = 0; i < resources.size(); i++) {
- if (!buffers[i]->ValuePtr()->empty()) {
- resources[i]->UpdateMemoryWithRawData(*buffers[i]->ValuePtr());
+ for (const auto& resource : GetResources()) {
+ if (!resource.first->ValuePtr()->empty()) {
+ resource.second->UpdateMemoryWithRawData(*resource.first->ValuePtr());
// If the resource is read-only, keep the buffer data; Amber won't copy
// read-only resources back into the host buffers, so it makes sense to
// leave the buffer intact.
if (!IsReadOnly())
- buffers[i]->ValuePtr()->clear();
+ resource.first->ValuePtr()->clear();
}
- resources[i]->CopyToDevice(command);
+ resource.second->CopyToDevice(command);
}
-
return {};
}
@@ -67,7 +59,7 @@ Result BufferBackedDescriptor::RecordCopyDataToHost(CommandBuffer* command) {
}
for (const auto& r : GetResources())
- r->CopyToHost(command);
+ r.second->CopyToHost(command);
}
return {};
@@ -79,11 +71,6 @@ Result BufferBackedDescriptor::MoveResourceToBufferOutput() {
return {};
auto resources = GetResources();
- auto buffers = GetAmberBuffers();
- if (resources.size() != buffers.size())
- return Result(
- "Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() resource "
- "and buffer vector sizes are not matching");
if (resources.empty()) {
return Result(
@@ -91,26 +78,25 @@ Result BufferBackedDescriptor::MoveResourceToBufferOutput() {
"transfer resource");
}
- for (size_t i = 0; i < resources.size(); i++) {
- void* resource_memory_ptr = resources[i]->HostAccessibleMemoryPtr();
+ for (const auto& resource : resources) {
+ void* resource_memory_ptr = resource.second->HostAccessibleMemoryPtr();
+ auto* buffer = resource.first;
if (!resource_memory_ptr) {
return Result(
"Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() "
"no host accessible memory pointer");
}
- if (!buffers[i]->ValuePtr()->empty()) {
+ if (!buffer->ValuePtr()->empty()) {
return Result(
"Vulkan: BufferBackedDescriptor::MoveResourceToBufferOutput() "
"output buffer is not empty");
}
- auto size_in_bytes = resources[i]->GetSizeInBytes();
- buffers[i]->SetElementCount(size_in_bytes /
- buffers[i]->GetFormat()->SizeInBytes());
- buffers[i]->ValuePtr()->resize(size_in_bytes);
- std::memcpy(buffers[i]->ValuePtr()->data(), resource_memory_ptr,
- size_in_bytes);
+ auto size_in_bytes = resource.second->GetSizeInBytes();
+ buffer->SetElementCount(size_in_bytes / buffer->GetFormat()->SizeInBytes());
+ buffer->ValuePtr()->resize(size_in_bytes);
+ std::memcpy(buffer->ValuePtr()->data(), resource_memory_ptr, size_in_bytes);
}
return {};
diff --git a/src/vulkan/buffer_backed_descriptor.h b/src/vulkan/buffer_backed_descriptor.h
index c626dd8..8f9df41 100644
--- a/src/vulkan/buffer_backed_descriptor.h
+++ b/src/vulkan/buffer_backed_descriptor.h
@@ -16,6 +16,7 @@
#define SRC_VULKAN_BUFFER_BACKED_DESCRIPTOR_H_
#include <memory>
+#include <utility>
#include <vector>
#include "amber/result.h"
@@ -51,7 +52,10 @@ class BufferBackedDescriptor : public Descriptor {
bool IsReadOnly() const;
protected:
- virtual std::vector<Resource*> GetResources() = 0;
+ /// Returns a list of unique transfer buffer resources. Note that this list
+ /// may contain less items than the |amber_buffers| vector contains if two or
+ /// more amber buffers use same Vulkan buffer.
+ virtual std::vector<std::pair<Buffer*, Resource*>> GetResources() = 0;
private:
std::vector<Buffer*> amber_buffers_;
diff --git a/src/vulkan/buffer_descriptor.cc b/src/vulkan/buffer_descriptor.cc
index 510565d..aa8b2dd 100644
--- a/src/vulkan/buffer_descriptor.cc
+++ b/src/vulkan/buffer_descriptor.cc
@@ -14,6 +14,8 @@
#include "src/vulkan/buffer_descriptor.h"
+#include <algorithm>
+#include <utility>
#include <vector>
#include "src/make_unique.h"
@@ -39,17 +41,23 @@ Result BufferDescriptor::CreateResourceIfNeeded() {
"only when |transfer_buffers| is empty");
}
- transfer_buffers_.reserve(GetAmberBuffers().size());
+ descriptor_offsets_.reserve(GetAmberBuffers().size());
+ descriptor_ranges_.reserve(GetAmberBuffers().size());
for (const auto& amber_buffer : GetAmberBuffers()) {
if (amber_buffer->ValuePtr()->empty())
continue;
- uint32_t size_in_bytes =
- amber_buffer ? static_cast<uint32_t>(amber_buffer->ValuePtr()->size())
- : 0;
- transfer_buffers_.emplace_back(MakeUnique<TransferBuffer>(
- device_, size_in_bytes, amber_buffer->GetFormat()));
+ // Check if the transfer buffer is already created.
+ if (transfer_buffers_.count(amber_buffer) > 0)
+ continue;
+
+ auto size_in_bytes =
+ static_cast<uint32_t>(amber_buffer->ValuePtr()->size());
+
+ auto transfer_buffer = MakeUnique<TransferBuffer>(
+ device_, size_in_bytes, amber_buffer->GetFormat());
+
VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
if (IsUniformBuffer() || IsUniformBufferDynamic()) {
@@ -64,9 +72,10 @@ Result BufferDescriptor::CreateResourceIfNeeded() {
return Result("Unexpected buffer type when deciding usage flags");
}
- Result r = transfer_buffers_.back()->Initialize(flags);
+ Result r = transfer_buffer->Initialize(flags);
if (!r.IsSuccess())
return r;
+ transfer_buffers_[amber_buffer] = std::move(transfer_buffer);
}
is_descriptor_set_update_needed_ = true;
@@ -89,12 +98,33 @@ void BufferDescriptor::UpdateDescriptorSetIfNeeded(
std::vector<VkDescriptorBufferInfo> buffer_infos;
std::vector<VkBufferView> buffer_views;
- for (const auto& buffer : transfer_buffers_) {
- VkDescriptorBufferInfo buffer_info;
- buffer_info.buffer = buffer->GetVkBuffer();
- buffer_info.offset = 0;
- buffer_info.range = VK_WHOLE_SIZE;
- buffer_infos.push_back(buffer_info);
+ // Create VkDescriptorBufferInfo for every descriptor buffer.
+ for (uint32_t i = 0; i < GetAmberBuffers().size(); i++) {
+ const auto& buffer = transfer_buffers_[GetAmberBuffers()[i]];
+ assert(buffer->GetVkBuffer() && "Unexpected descriptor type");
+ // Add buffer infos for uniform and storage buffers.
+ if (IsUniformBuffer() || IsUniformBufferDynamic() || IsStorageBuffer() ||
+ IsStorageBufferDynamic()) {
+ uint64_t range = descriptor_ranges_[i];
+ // If dynamic offset is used, we must change range with VK_WHOLE_SIZE to
+ // an actual range.
+ // From vulkan spec: For each dynamic uniform or storage buffer binding in
+ // pDescriptorSets, the sum of the effective offset, as defined above, and
+ // the range of the binding must be less than or equal to the size of the
+ // buffer.
+ if ((IsUniformBufferDynamic() || IsStorageBufferDynamic()) &&
+ descriptor_ranges_[i] == VK_WHOLE_SIZE) {
+ range = buffer->GetSizeInBytes() - dynamic_offsets_[i] -
+ descriptor_offsets_[i];
+ }
+
+ VkDescriptorBufferInfo buffer_info;
+ buffer_info.buffer = buffer->GetVkBuffer();
+ buffer_info.offset = descriptor_offsets_[i];
+ buffer_info.range = range;
+
+ buffer_infos.push_back(buffer_info);
+ }
if (IsUniformTexelBuffer() || IsStorageTexelBuffer()) {
buffer_views.push_back(*buffer->GetVkBufferView());
@@ -106,7 +136,7 @@ void BufferDescriptor::UpdateDescriptorSetIfNeeded(
write.dstSet = descriptor_set;
write.dstBinding = binding_;
write.dstArrayElement = 0;
- write.descriptorCount = static_cast<uint32_t>(buffer_infos.size());
+ write.descriptorCount = static_cast<uint32_t>(GetAmberBuffers().size());
write.descriptorType = GetVkDescriptorType();
write.pBufferInfo = buffer_infos.data();
write.pTexelBufferView = buffer_views.data();
@@ -116,10 +146,20 @@ void BufferDescriptor::UpdateDescriptorSetIfNeeded(
is_descriptor_set_update_needed_ = false;
}
-std::vector<Resource*> BufferDescriptor::GetResources() {
- std::vector<Resource*> ret;
- for (auto& b : transfer_buffers_) {
- ret.push_back(b.get());
+std::vector<std::pair<Buffer*, Resource*>> BufferDescriptor::GetResources() {
+ std::vector<std::pair<Buffer*, Resource*>> ret;
+ // Add unique amber buffers and related transfer buffers to the vector.
+ for (const auto& amber_buffer : GetAmberBuffers()) {
+ // Skip duplicate values.
+ const auto& image =
+ std::find_if(ret.begin(), ret.end(),
+ [&](const std::pair<Buffer*, Resource*>& buffer) {
+ return buffer.first == amber_buffer;
+ });
+ if (image != ret.end())
+ continue;
+
+ ret.emplace_back(amber_buffer, transfer_buffers_[amber_buffer].get());
}
return ret;
}
diff --git a/src/vulkan/buffer_descriptor.h b/src/vulkan/buffer_descriptor.h
index d481ae5..2c3f390 100644
--- a/src/vulkan/buffer_descriptor.h
+++ b/src/vulkan/buffer_descriptor.h
@@ -16,6 +16,8 @@
#define SRC_VULKAN_BUFFER_DESCRIPTOR_H_
#include <memory>
+#include <unordered_map>
+#include <utility>
#include <vector>
#include "amber/result.h"
@@ -50,14 +52,30 @@ class BufferDescriptor : public BufferBackedDescriptor {
return dynamic_offsets_;
}
void AddDynamicOffset(uint32_t offset) { dynamic_offsets_.push_back(offset); }
+ std::vector<uint64_t> GetDescriptorOffsets() override {
+ return descriptor_offsets_;
+ }
+ void AddDescriptorOffset(uint64_t descriptor_offset) {
+ descriptor_offsets_.push_back(descriptor_offset);
+ }
+ std::vector<uint64_t> GetDescriptorRanges() override {
+ return descriptor_ranges_;
+ }
+ void AddDescriptorRange(uint64_t descriptor_range) {
+ descriptor_ranges_.push_back(descriptor_range);
+ }
+
BufferDescriptor* AsBufferDescriptor() override { return this; }
protected:
- std::vector<Resource*> GetResources() override;
+ std::vector<std::pair<Buffer*, Resource*>> GetResources() override;
private:
- std::vector<std::unique_ptr<TransferBuffer>> transfer_buffers_;
+ std::unordered_map<Buffer*, std::unique_ptr<TransferBuffer>>
+ transfer_buffers_;
std::vector<uint32_t> dynamic_offsets_;
+ std::vector<VkDeviceSize> descriptor_offsets_;
+ std::vector<VkDeviceSize> descriptor_ranges_;
};
} // namespace vulkan
diff --git a/src/vulkan/descriptor.h b/src/vulkan/descriptor.h
index ba702ff..03434a3 100644
--- a/src/vulkan/descriptor.h
+++ b/src/vulkan/descriptor.h
@@ -64,6 +64,8 @@ class Descriptor {
virtual Result AddToBuffer(const std::vector<Value>&, uint32_t) { return {}; }
virtual uint32_t GetDescriptorCount() { return 1; }
virtual std::vector<uint32_t> GetDynamicOffsets() { return {}; }
+ virtual std::vector<VkDeviceSize> GetDescriptorOffsets() { return {}; }
+ virtual std::vector<VkDeviceSize> GetDescriptorRanges() { return {}; }
virtual BufferDescriptor* AsBufferDescriptor() { return nullptr; }
virtual BufferBackedDescriptor* AsBufferBackedDescriptor() { return nullptr; }
virtual SamplerDescriptor* AsSamplerDescriptor() { return nullptr; }
diff --git a/src/vulkan/engine_vulkan.cc b/src/vulkan/engine_vulkan.cc
index 536993f..6bed716 100644
--- a/src/vulkan/engine_vulkan.cc
+++ b/src/vulkan/engine_vulkan.cc
@@ -254,6 +254,8 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) {
cmd->SetBinding(buf_info.binding);
cmd->SetBaseMipLevel(buf_info.base_mip_level);
cmd->SetDynamicOffset(buf_info.dynamic_offset);
+ cmd->SetDescriptorOffset(buf_info.descriptor_offset);
+ cmd->SetDescriptorRange(buf_info.descriptor_range);
cmd->SetBuffer(buf_info.buffer);
cmd->SetSampler(buf_info.sampler);
diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc
index b83bea1..5536440 100644
--- a/src/vulkan/graphics_pipeline.cc
+++ b/src/vulkan/graphics_pipeline.cc
@@ -579,6 +579,16 @@ Result GraphicsPipeline::CreateVkGraphicsPipeline(
0, 0, static_cast<float>(frame_width_), static_cast<float>(frame_height_),
0, 1};
+ if (pipeline_data->HasViewportData()) {
+ Viewport vp = pipeline_data->GetViewport();
+ viewport.x = vp.x;
+ viewport.y = vp.y;
+ viewport.width = vp.w;
+ viewport.height = vp.h;
+ viewport.minDepth = vp.mind;
+ viewport.maxDepth = vp.maxd;
+ }
+
VkRect2D scissor = {{0, 0}, {frame_width_, frame_height_}};
VkPipelineViewportStateCreateInfo viewport_info =
diff --git a/src/vulkan/image_descriptor.cc b/src/vulkan/image_descriptor.cc
index 550ce0b..94e67d0 100644
--- a/src/vulkan/image_descriptor.cc
+++ b/src/vulkan/image_descriptor.cc
@@ -14,6 +14,10 @@
#include "src/vulkan/image_descriptor.h"
+#include <algorithm>
+#include <unordered_map>
+#include <utility>
+
#include "src/vulkan/device.h"
#include "src/vulkan/resource.h"
@@ -34,9 +38,12 @@ ImageDescriptor::~ImageDescriptor() = default;
Result ImageDescriptor::RecordCopyDataToResourceIfNeeded(
CommandBuffer* command) {
- for (auto& image : transfer_images_) {
- image->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
- VK_PIPELINE_STAGE_TRANSFER_BIT);
+ const auto transfer_images = GetResources();
+ for (const auto& image : transfer_images) {
+ // Static cast is safe, because the type is known to be TransferImage*.
+ static_cast<TransferImage*>(image.second)
+ ->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
}
Result r = BufferBackedDescriptor::RecordCopyDataToResourceIfNeeded(command);
@@ -44,9 +51,10 @@ Result ImageDescriptor::RecordCopyDataToResourceIfNeeded(
return r;
// Just do this as early as possible.
- for (auto& image : transfer_images_) {
- image->ImageBarrier(command, VK_IMAGE_LAYOUT_GENERAL,
- VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
+ for (const auto& image : transfer_images) {
+ static_cast<TransferImage*>(image.second)
+ ->ImageBarrier(command, VK_IMAGE_LAYOUT_GENERAL,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
}
return {};
@@ -59,12 +67,14 @@ Result ImageDescriptor::CreateResourceIfNeeded() {
"only when |transfer_images| is empty");
}
- transfer_images_.reserve(GetAmberBuffers().size());
-
for (const auto& amber_buffer : GetAmberBuffers()) {
if (amber_buffer->ValuePtr()->empty())
continue;
+ // Check if the transfer image is already created.
+ if (transfer_images_.count(amber_buffer) > 0)
+ continue;
+
// Default to 2D image.
VkImageType image_type = VK_IMAGE_TYPE_2D;
switch (amber_buffer->GetImageDimension()) {
@@ -93,11 +103,11 @@ Result ImageDescriptor::CreateResourceIfNeeded() {
aspect = VK_IMAGE_ASPECT_COLOR_BIT;
}
- transfer_images_.emplace_back(MakeUnique<TransferImage>(
+ auto transfer_image = MakeUnique<TransferImage>(
device_, *fmt, aspect, image_type, amber_buffer->GetWidth(),
amber_buffer->GetHeight(), amber_buffer->GetDepth(),
amber_buffer->GetMipLevels(), base_mip_level_, VK_REMAINING_MIP_LEVELS,
- amber_buffer->GetSamples()));
+ amber_buffer->GetSamples());
VkImageUsageFlags usage =
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
@@ -109,10 +119,11 @@ Result ImageDescriptor::CreateResourceIfNeeded() {
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
- Result r = transfer_images_.back()->Initialize(usage);
-
+ Result r = transfer_image->Initialize(usage);
if (!r.IsSuccess())
return r;
+
+ transfer_images_[amber_buffer] = std::move(transfer_image);
}
if (amber_sampler_) {
@@ -127,9 +138,10 @@ Result ImageDescriptor::CreateResourceIfNeeded() {
Result ImageDescriptor::RecordCopyDataToHost(CommandBuffer* command) {
if (!IsReadOnly()) {
- for (auto& image : transfer_images_) {
- image->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
- VK_PIPELINE_STAGE_TRANSFER_BIT);
+ for (auto& image : GetResources()) {
+ static_cast<TransferImage*>(image.second)
+ ->ImageBarrier(command, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
}
BufferBackedDescriptor::RecordCopyDataToHost(command);
@@ -156,7 +168,9 @@ void ImageDescriptor::UpdateDescriptorSetIfNeeded(
std::vector<VkDescriptorImageInfo> image_infos;
- for (const auto& image : transfer_images_) {
+ // Create VkDescriptorImageInfo for every descriptor image.
+ for (const auto& amber_buffer : GetAmberBuffers()) {
+ const auto& image = transfer_images_[amber_buffer];
VkDescriptorImageInfo image_info = {vulkan_sampler_.GetVkSampler(),
image->GetVkImageView(), layout};
image_infos.push_back(image_info);
@@ -177,10 +191,20 @@ void ImageDescriptor::UpdateDescriptorSetIfNeeded(
is_descriptor_set_update_needed_ = false;
}
-std::vector<Resource*> ImageDescriptor::GetResources() {
- std::vector<Resource*> ret;
- for (auto& i : transfer_images_) {
- ret.push_back(i.get());
+std::vector<std::pair<Buffer*, Resource*>> ImageDescriptor::GetResources() {
+ std::vector<std::pair<Buffer*, Resource*>> ret;
+ // Add unique amber buffers and related transfer images to the vector.
+ for (const auto& amber_buffer : GetAmberBuffers()) {
+ // Skip duplicate values.
+ const auto& image =
+ std::find_if(ret.begin(), ret.end(),
+ [&](const std::pair<Buffer*, Resource*>& buffer) {
+ return buffer.first == amber_buffer;
+ });
+ if (image != ret.end())
+ continue;
+
+ ret.emplace_back(amber_buffer, transfer_images_[amber_buffer].get());
}
return ret;
}
diff --git a/src/vulkan/image_descriptor.h b/src/vulkan/image_descriptor.h
index 5f8dd0e..908ed17 100644
--- a/src/vulkan/image_descriptor.h
+++ b/src/vulkan/image_descriptor.h
@@ -16,6 +16,8 @@
#define SRC_VULKAN_IMAGE_DESCRIPTOR_H_
#include <memory>
+#include <unordered_map>
+#include <utility>
#include <vector>
#include "src/vulkan/buffer_backed_descriptor.h"
@@ -43,11 +45,11 @@ class ImageDescriptor : public BufferBackedDescriptor {
void SetAmberSampler(amber::Sampler* sampler) { amber_sampler_ = sampler; }
protected:
- std::vector<Resource*> GetResources() override;
+ std::vector<std::pair<Buffer*, Resource*>> GetResources() override;
private:
uint32_t base_mip_level_ = 0;
- std::vector<std::unique_ptr<TransferImage>> transfer_images_;
+ std::unordered_map<Buffer*, std::unique_ptr<TransferImage>> transfer_images_;
amber::Sampler* amber_sampler_ = nullptr;
amber::vulkan::Sampler vulkan_sampler_;
};
diff --git a/src/vulkan/pipeline.cc b/src/vulkan/pipeline.cc
index e0b875c..259f375 100644
--- a/src/vulkan/pipeline.cc
+++ b/src/vulkan/pipeline.cc
@@ -322,6 +322,7 @@ Result Pipeline::AddBufferDescriptor(const BufferCommand* cmd) {
cmd->GetDescriptorSet(), cmd->GetBinding());
if (cmd->IsCombinedImageSampler())
image_desc->SetAmberSampler(cmd->GetSampler());
+
descriptors.push_back(std::move(image_desc));
} else {
auto buffer_desc = MakeUnique<BufferDescriptor>(
@@ -336,18 +337,18 @@ Result Pipeline::AddBufferDescriptor(const BufferCommand* cmd) {
"Descriptors bound to the same binding needs to have matching "
"descriptor types");
}
- // Check that the buffer is not added already.
- const auto& buffers = desc->AsBufferBackedDescriptor()->GetAmberBuffers();
- if (std::find(buffers.begin(), buffers.end(), cmd->GetBuffer()) !=
- buffers.end()) {
- return Result("Buffer has been added already");
- }
desc->AsBufferBackedDescriptor()->AddAmberBuffer(cmd->GetBuffer());
}
if (cmd->IsUniformDynamic() || cmd->IsSSBODynamic())
desc->AsBufferDescriptor()->AddDynamicOffset(cmd->GetDynamicOffset());
+ if (cmd->IsUniform() || cmd->IsUniformDynamic() || cmd->IsSSBO() ||
+ cmd->IsSSBODynamic()) {
+ desc->AsBufferDescriptor()->AddDescriptorOffset(cmd->GetDescriptorOffset());
+ desc->AsBufferDescriptor()->AddDescriptorRange(cmd->GetDescriptorRange());
+ }
+
if (cmd->IsSSBO() && !desc->IsStorageBuffer()) {
return Result(
"Vulkan::AddBufferDescriptor BufferCommand for SSBO uses wrong "
diff --git a/src/vulkan/pipeline_test.cc b/src/vulkan/pipeline_test.cc
index 53eca44..9f60174 100644
--- a/src/vulkan/pipeline_test.cc
+++ b/src/vulkan/pipeline_test.cc
@@ -44,9 +44,9 @@ TEST_F(VulkanPipelineTest, AddBufferDescriptorAddBufferTwice) {
&amber_pipeline);
Result r = pipeline.AddBufferDescriptor(cmd.get());
ASSERT_TRUE(r.IsSuccess()) << r.Error();
- // Adding same buffer again should fail.
+ // Adding same buffer again shouldn't fail.
r = pipeline.AddBufferDescriptor(cmd.get());
- ASSERT_FALSE(r.IsSuccess());
+ ASSERT_TRUE(r.IsSuccess());
}
} // namespace vulkan
diff --git a/tests/cases/compute_descriptor_array_ssbo.amber b/tests/cases/compute_descriptor_array_ssbo.amber
index bd4c773..0eb6b7d 100644
--- a/tests/cases/compute_descriptor_array_ssbo.amber
+++ b/tests/cases/compute_descriptor_array_ssbo.amber
@@ -20,12 +20,19 @@ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0) buffer block0
{
int data;
-} ssbo_array[2];
+} ssbo_array_0[2];
+
+layout(binding = 1) buffer block1
+{
+ int data;
+} ssbo_array_1[2];
void main()
{
- ssbo_array[0].data = 1;
- ssbo_array[1].data = 2;
+ ssbo_array_0[0].data = 1;
+ ssbo_array_0[1].data = 2;
+ ssbo_array_1[0].data = 3;
+ ssbo_array_1[1].data = 4;
}
END
@@ -37,13 +44,21 @@ BUFFER buf1 DATA_TYPE int32 DATA
0
END
+# The Vulkan spec lists the maximum value of minStorageBufferOffsetAlignment
+# (i.e. the maximum possible alignment requirement) as 256 bytes, so we will use
+# buffer with size of 65 int32 values = 260 bytes.
+BUFFER buf2 DATA_TYPE int32 SIZE 65 FILL 0
+
PIPELINE compute pipeline
ATTACH compute_shader
BIND BUFFER_ARRAY buf0 buf1 AS storage DESCRIPTOR_SET 0 BINDING 0
+ BIND BUFFER_ARRAY buf2 buf2 AS storage DESCRIPTOR_SET 0 BINDING 1 DESCRIPTOR_OFFSET 0 256 DESCRIPTOR_RANGE 256 4
END
RUN pipeline 1 1 1
EXPECT buf0 IDX 0 EQ 1
EXPECT buf1 IDX 0 EQ 2
+EXPECT buf2 IDX 0 EQ 3
+EXPECT buf2 IDX 256 EQ 4
diff --git a/tests/cases/compute_descriptor_array_storagetexelbuffer.amber b/tests/cases/compute_descriptor_array_storagetexelbuffer.amber
index d4a1994..3ddae3c 100644
--- a/tests/cases/compute_descriptor_array_storagetexelbuffer.amber
+++ b/tests/cases/compute_descriptor_array_storagetexelbuffer.amber
@@ -17,13 +17,20 @@ SHADER compute compute_shader GLSL
#version 430
layout(local_size_x=4,local_size_y=1) in;
uniform layout(set=0, binding=0) samplerBuffer texelsIn;
-uniform layout(set=0, binding=1, rgba32f) imageBuffer texelsOut[2];
+uniform layout(set=0, binding=1, rgba32f) imageBuffer texelsOut_0[2];
+uniform layout(set=0, binding=2, rgba32f) imageBuffer texelsOut_1[2];
void main ()
{
vec4 color = texelFetch(texelsIn, int(gl_GlobalInvocationID));
- imageStore(texelsOut[0], int(gl_GlobalInvocationID), color);
- imageStore(texelsOut[1], int(gl_GlobalInvocationID), color * 2.0);
+ imageStore(texelsOut_0[0], int(gl_GlobalInvocationID), color);
+ imageStore(texelsOut_0[1], int(gl_GlobalInvocationID), color * 2.0);
+ if (int(gl_GlobalInvocationID) < 2) {
+ imageStore(texelsOut_1[0], int(gl_GlobalInvocationID), color * 3.0);
+ }
+ else {
+ imageStore(texelsOut_1[1], int(gl_GlobalInvocationID), color * 4.0);
+ }
}
END
@@ -36,6 +43,7 @@ END
BUFFER texel_buffer_out0 DATA_TYPE R32G32B32A32_SFLOAT SIZE 4 FILL 0
BUFFER texel_buffer_out1 DATA_TYPE R32G32B32A32_SFLOAT SIZE 4 FILL 0
+BUFFER texel_buffer_out2 DATA_TYPE R32G32B32A32_SFLOAT SIZE 4 FILL 0
BUFFER ref0 DATA_TYPE R32G32B32A32_SFLOAT DATA
1.0 0.0 0.0 1.0
@@ -51,13 +59,22 @@ BUFFER ref1 DATA_TYPE R32G32B32A32_SFLOAT DATA
0.0 2.0 2.0 2.0
END
+BUFFER ref2 DATA_TYPE R32G32B32A32_SFLOAT DATA
+3.0 0.0 0.0 3.0
+0.0 3.0 0.0 3.0
+0.0 0.0 4.0 4.0
+0.0 4.0 4.0 4.0
+END
+
PIPELINE compute pipeline
ATTACH compute_shader
BIND BUFFER texel_buffer_in AS uniform_texel_buffer DESCRIPTOR_SET 0 BINDING 0
BIND BUFFER_ARRAY texel_buffer_out0 texel_buffer_out1 AS storage_texel_buffer DESCRIPTOR_SET 0 BINDING 1
+ BIND BUFFER_ARRAY texel_buffer_out2 texel_buffer_out2 AS storage_texel_buffer DESCRIPTOR_SET 0 BINDING 2
END
RUN pipeline 1 1 1
EXPECT texel_buffer_out0 EQ_BUFFER ref0
EXPECT texel_buffer_out1 EQ_BUFFER ref1
+EXPECT texel_buffer_out2 EQ_BUFFER ref2
diff --git a/tests/cases/compute_dynamic_buffers.amber b/tests/cases/compute_dynamic_buffers.amber
index 9412e30..9dd4149 100644
--- a/tests/cases/compute_dynamic_buffers.amber
+++ b/tests/cases/compute_dynamic_buffers.amber
@@ -25,11 +25,16 @@ layout(set = 0, binding = 1) buffer block1
{
vec4 data1;
};
+layout(set = 0, binding = 2) buffer block2
+{
+ vec4 data2;
+};
void main()
{
data0 = vec4(1, 2, 3, 4);
data1 = vec4(5, 6, 7, 8);
+ data2 = vec4(9, 10, 11, 12);
}
END
@@ -39,6 +44,7 @@ END
# after the alignment (256 / 16 + 1).
BUFFER buf0 DATA_TYPE vec4<float> SIZE 17 FILL 0.0
BUFFER buf1 DATA_TYPE vec4<float> SIZE 17 FILL 0.0
+BUFFER buf2 DATA_TYPE vec4<float> SIZE 33 FILL 0.0
PIPELINE compute pipeline
ATTACH compute_shader
@@ -46,9 +52,12 @@ PIPELINE compute pipeline
BIND BUFFER buf0 AS storage_dynamic DESCRIPTOR_SET 0 BINDING 0 OFFSET 0
# Using the maximum possible required offset alignment of 256 bytes to support all implementations.
BIND BUFFER buf1 AS storage_dynamic DESCRIPTOR_SET 0 BINDING 1 OFFSET 256
+ # Same as above, but with buffer offset of 256 bytes (total offset of 512).
+ BIND BUFFER buf2 AS storage_dynamic DESCRIPTOR_SET 0 BINDING 2 OFFSET 256 DESCRIPTOR_OFFSET 256
END
RUN pipeline 1 1 1
EXPECT buf0 IDX 0 EQ 1.0 2.0 3.0 4.0
EXPECT buf1 IDX 256 EQ 5.0 6.0 7.0 8.0
+EXPECT buf2 IDX 512 EQ 9.0 10.0 11.0 12.0
diff --git a/tests/cases/compute_dynamic_buffers_descriptor_array.amber b/tests/cases/compute_dynamic_buffers_descriptor_array.amber
index fe07b6c..ccc4c42 100644
--- a/tests/cases/compute_dynamic_buffers_descriptor_array.amber
+++ b/tests/cases/compute_dynamic_buffers_descriptor_array.amber
@@ -19,12 +19,18 @@ SHADER compute compute_shader GLSL
layout(set = 0, binding = 0) buffer block0
{
vec4 data;
-} ssbo_array[2];
+} ssbo_array_0[2];
+layout(set = 0, binding = 1) buffer block1
+{
+ int data;
+} ssbo_array_1[2];
void main()
{
- ssbo_array[0].data = vec4(1, 2, 3, 4);
- ssbo_array[1].data = vec4(5, 6, 7, 8);
+ ssbo_array_0[0].data = vec4(1, 2, 3, 4);
+ ssbo_array_0[1].data = vec4(5, 6, 7, 8);
+ ssbo_array_1[0].data = 9;
+ ssbo_array_1[1].data = 10;
}
END
@@ -34,15 +40,20 @@ END
# after the alignment (256 / 16 + 1).
BUFFER buf0 DATA_TYPE vec4<float> SIZE 17 FILL 0.0
BUFFER buf1 DATA_TYPE vec4<float> SIZE 17 FILL 0.0
+# Same as above, but with int32 and double size: 512 / 4 + 1
+BUFFER buf2 DATA_TYPE int32 SIZE 129 FILL 0
PIPELINE compute pipeline
ATTACH compute_shader
# Using the maximum possible required offset alignment of 256 bytes to support all implementations.
BIND BUFFER_ARRAY buf0 buf1 AS storage_dynamic DESCRIPTOR_SET 0 BINDING 0 OFFSET 0 256
+ BIND BUFFER_ARRAY buf2 buf2 AS storage_dynamic DESCRIPTOR_SET 0 BINDING 1 OFFSET 0 256 DESCRIPTOR_OFFSET 0 256
END
RUN pipeline 1 1 1
EXPECT buf0 IDX 0 EQ 1.0 2.0 3.0 4.0
EXPECT buf1 IDX 256 EQ 5.0 6.0 7.0 8.0
+EXPECT buf2 IDX 0 EQ 9
+EXPECT buf2 IDX 512 EQ 10
diff --git a/tests/cases/graphics_descriptor_array_combined_image_sampler.amber b/tests/cases/graphics_descriptor_array_combined_image_sampler.amber
index d7fe3c0..e80ca1e 100644
--- a/tests/cases/graphics_descriptor_array_combined_image_sampler.amber
+++ b/tests/cases/graphics_descriptor_array_combined_image_sampler.amber
@@ -40,10 +40,16 @@ 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) sampler2D tex_sampler[2];
+uniform layout(set=0, binding=0) sampler2D tex_sampler_0[2];
+uniform layout(set=0, binding=1) sampler2D tex_sampler_1[2];
void main() {
- color_out = texture(tex_sampler[0], texcoords_in);
- color_out = color_out + texture(tex_sampler[1], texcoords_in);
+ vec4 tex_0_color = texture(tex_sampler_0[0], texcoords_in);
+ color_out = tex_0_color + texture(tex_sampler_0[1], texcoords_in);
+ // tex_sampler_1[0] and tex_sampler_1[1] should be equal to tex_sampler_0[0]
+ if (tex_0_color != texture(tex_sampler_1[0], texcoords_in) ||
+ tex_0_color != texture(tex_sampler_1[1], texcoords_in)) {
+ color_out = vec4(1.0);
+ }
}
END
@@ -75,6 +81,7 @@ PIPELINE graphics pipeline
ATTACH vert_shader_tex
ATTACH frag_shader_tex
BIND BUFFER_ARRAY texture0 texture1 AS combined_image_sampler SAMPLER sampler DESCRIPTOR_SET 0 BINDING 0
+ BIND BUFFER_ARRAY texture0 texture0 AS combined_image_sampler SAMPLER sampler DESCRIPTOR_SET 0 BINDING 1
VERTEX_DATA position LOCATION 0
VERTEX_DATA texcoords LOCATION 1
BIND BUFFER framebuffer AS color LOCATION 0
diff --git a/tests/cases/graphics_descriptor_array_sampled_image.amber b/tests/cases/graphics_descriptor_array_sampled_image.amber
index 497f6da..3342054 100644
--- a/tests/cases/graphics_descriptor_array_sampled_image.amber
+++ b/tests/cases/graphics_descriptor_array_sampled_image.amber
@@ -40,11 +40,18 @@ 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[2];
+uniform layout(set=0, binding=0) texture2D tex_0[2];
uniform layout(set=0, binding=1) sampler tex_sampler;
+uniform layout(set=0, binding=2) texture2D tex_1[2];
void main() {
- color_out = texture(sampler2D(tex[0], tex_sampler), texcoords_in);
- color_out += texture(sampler2D(tex[1], tex_sampler), texcoords_in);
+ vec4 tex_0_color = texture(sampler2D(tex_0[0], tex_sampler), texcoords_in);
+ color_out = tex_0_color;
+ color_out += texture(sampler2D(tex_0[1], tex_sampler), texcoords_in);
+ // tex_1[0] and tex_1[1] should be equal to tex_0[0].
+ if (tex_0_color != texture(sampler2D(tex_1[0], tex_sampler), texcoords_in) ||
+ tex_0_color != texture(sampler2D(tex_1[1], tex_sampler), texcoords_in)) {
+ color_out = vec4(1.0);
+ }
}
END
@@ -77,6 +84,7 @@ PIPELINE graphics pipeline
ATTACH frag_shader_tex
BIND BUFFER_ARRAY texture0 texture1 AS sampled_image DESCRIPTOR_SET 0 BINDING 0
BIND SAMPLER sampler DESCRIPTOR_SET 0 BINDING 1
+ BIND BUFFER_ARRAY texture0 texture0 AS sampled_image DESCRIPTOR_SET 0 BINDING 2
VERTEX_DATA position LOCATION 0
VERTEX_DATA texcoords LOCATION 1
BIND BUFFER framebuffer AS color LOCATION 0
diff --git a/tests/cases/non_default_entry_point.amber b/tests/cases/non_default_entry_point.amber
index 36b2135..3a0892f 100644
--- a/tests/cases/non_default_entry_point.amber
+++ b/tests/cases/non_default_entry_point.amber
@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+DEVICE_EXTENSION VK_KHR_storage_buffer_storage_class
+
SHADER compute my_shader SPIRV-ASM
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
diff --git a/tests/cases/shader_specialization.amber b/tests/cases/shader_specialization.amber
index 1db2a4f..4514b6f 100644
--- a/tests/cases/shader_specialization.amber
+++ b/tests/cases/shader_specialization.amber
@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+DEVICE_EXTENSION VK_KHR_storage_buffer_storage_class
+
SHADER compute my_compute SPIRV-ASM
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
diff --git a/tests/cases/storage16.amber b/tests/cases/storage16.amber
index 5cf8113..36a62dc 100644
--- a/tests/cases/storage16.amber
+++ b/tests/cases/storage16.amber
@@ -19,6 +19,7 @@ DEVICE_EXTENSION VK_KHR_16bit_storage
DEVICE_FEATURE shaderInt16
DEVICE_FEATURE Storage16BitFeatures.uniformAndStorageBuffer16BitAccess
DEVICE_FEATURE Storage16BitFeatures.storagePushConstant16
+DEVICE_FEATURE Storage16BitFeatures.storageBuffer16BitAccess
SHADER compute comp_shader GLSL
#version 450
diff --git a/tests/cases/viewport.amber b/tests/cases/viewport.amber
new file mode 100644
index 0000000..ca9b0a5
--- /dev/null
+++ b/tests/cases/viewport.amber
@@ -0,0 +1,59 @@
+#!amber
+#
+# Copyright 2021 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 GLSL
+#version 430
+
+layout(location = 0) in vec4 position;
+
+void main() {
+ gl_Position = vec4(position.xy, 0.5, 1.0);
+}
+END
+
+SHADER fragment frag_shader GLSL
+#version 430
+
+layout(location = 0) out vec4 final_color;
+
+void main() {
+ final_color = vec4(0, 1, 0, 1);
+}
+END
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+
+PIPELINE graphics pipeline1
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ FRAMEBUFFER_SIZE 256 256
+ VIEWPORT 10.0 10.0 SIZE 100.0 100.0
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+END
+
+CLEAR_COLOR pipeline1 255 255 255 255
+CLEAR pipeline1
+RUN pipeline1 DRAW_RECT POS 0 0 SIZE 256 256
+
+# Check within the viewport
+EXPECT framebuffer IDX 10 10 SIZE 100 100 EQ_RGBA 0 255 0 255
+# Check the borders were untouched
+EXPECT framebuffer IDX 0 0 SIZE 10 256 EQ_RGBA 255 255 255 255
+EXPECT framebuffer IDX 110 0 SIZE 146 256 EQ_RGBA 255 255 255 255
+EXPECT framebuffer IDX 10 0 SIZE 100 10 EQ_RGBA 255 255 255 255
+EXPECT framebuffer IDX 10 110 SIZE 100 146 EQ_RGBA 255 255 255 255
diff --git a/tests/cases/viewport_depth.amber b/tests/cases/viewport_depth.amber
new file mode 100644
index 0000000..2bf7a9e
--- /dev/null
+++ b/tests/cases/viewport_depth.amber
@@ -0,0 +1,61 @@
+#!amber
+#
+# Copyright 2021 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 GLSL
+#version 430
+
+layout(location = 0) in vec4 position;
+
+void main() {
+ gl_Position = vec4(position.xy, 0.5, 1.0);
+}
+END
+
+SHADER fragment frag_shader GLSL
+#version 430
+
+layout(location = 0) out vec4 final_color;
+
+void main() {
+ final_color = vec4(0, 1, 0, 1);
+}
+END
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+BUFFER depth FORMAT D32_SFLOAT
+
+PIPELINE graphics pipeline1
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ DEPTH
+ TEST on
+ WRITE on
+ END
+
+ FRAMEBUFFER_SIZE 256 256
+ VIEWPORT 0.0 0.0 SIZE 256.0 256.0 MIN_DEPTH 0.3 MAX_DEPTH 0.9
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+ BIND BUFFER depth AS depth_stencil
+END
+
+CLEAR_DEPTH pipeline1 1.0
+CLEAR_COLOR pipeline1 255 255 255 255
+CLEAR pipeline1
+RUN pipeline1 DRAW_RECT POS 0 0 SIZE 256 256
+
+EXPECT depth IDX 0 TOLERANCE 1.0e-6 EQ 0.6