diff --git a/docs/amber_script.md b/docs/amber_script.md index fbe2c8cbe..f5c058c7a 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -243,8 +243,12 @@ BUFFER {name} DATA_TYPE {type} {STD140 | STD430} SIZE _size_in_items_ \ # Creates a buffer which will store the given `FORMAT` of data. These # buffers are used as image and depth buffers in the `PIPELINE` commands. # The buffer will be sized based on the `RENDER_SIZE` of the `PIPELINE`. +# For multisampled images use value greater than one for `SAMPLES`. Allowed +# sample counts are 1, 2, 4, 8, 16, 32, and 64. Note that Amber doesn't +# preserve multisampled images across pipelines. BUFFER {name} FORMAT {format_string} \ - [ MIP_LEVELS _mip_levels_ (default 1) ] + [ MIP_LEVELS _mip_levels_ (default 1) ] \ + [ SAMPLES _samples_ (default 1) ] # Load buffer data from a PNG image with file name specified by `FILE`. # The file path is relative to the script file being run. Format specified @@ -266,6 +270,7 @@ attributes. # Specify an image buffer with a format. HEIGHT is necessary for DIM_2D and # DIM_3D. DEPTH is necessary for DIM_3D. IMAGE {name} FORMAT {format_string} [ MIP_LEVELS _mip_levels_ (default 1) ] \ + [ SAMPLES _samples_ (default 1) ] \ {dimensionality} \ WIDTH {w} [ HEIGHT {h} [ DEPTH {d} ] ] \ {initializer} diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index eda31f411..0a3b59be7 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -14,6 +14,7 @@ #include "src/amberscript/parser.h" +#include #include #include #include @@ -141,17 +142,6 @@ AddressMode StrToAddressMode(std::string str) { return AddressMode::kUnknown; } -ImageDimension StrToImageDimension(const std::string& str) { - if (str == "DIM_1D") - return ImageDimension::k1D; - if (str == "DIM_2D") - return ImageDimension::k2D; - if (str == "DIM_3D") - return ImageDimension::k3D; - - return ImageDimension::kUnknown; -} - CompareOp StrToCompareOp(const std::string& str) { if (str == "never") return CompareOp::kNever; @@ -263,6 +253,13 @@ Result ParseBufferData(Buffer* buffer, return {}; } +constexpr uint32_t valid_samples[] = {1, 2, 4, 8, 16, 32, 64}; + +bool IsValidSampleCount(uint32_t samples) { + return (std::find(std::begin(valid_samples), std::end(valid_samples), + samples) != std::end(valid_samples)); +} + } // namespace Parser::Parser() : amber::Parser(nullptr) {} @@ -1811,6 +1808,17 @@ Result Parser::ParseBuffer() { if (!r.IsSuccess()) return r; + } else if (token->AsString() == "SAMPLES") { + tokenizer_->NextToken(); + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("expected integer value for SAMPLES"); + + const uint32_t samples = token->AsUint32(); + if (!IsValidSampleCount(samples)) + return Result("invalid sample count: " + token->ToOriginalString()); + + buffer->SetSamples(samples); } else { break; } @@ -1837,110 +1845,117 @@ Result Parser::ParseImage() { if (name == "DATA_TYPE" || name == "FORMAT") return Result("missing IMAGE name"); - token = tokenizer_->NextToken(); - if (!token->IsIdentifier()) - return Result("invalid IMAGE command provided"); - std::unique_ptr buffer = MakeUnique(); buffer->SetName(name); - auto& cmd = token->AsString(); - if (cmd == "DATA_TYPE") { - token = tokenizer_->NextToken(); - if (!token->IsIdentifier()) - return Result("IMAGE invalid data type"); + bool width_set = false; + bool height_set = false; + bool depth_set = false; - auto type = script_->ParseType(token->AsString()); - std::unique_ptr fmt; - if (type != nullptr) { - fmt = MakeUnique(type); - buffer->SetFormat(fmt.get()); - } else { - auto new_type = ToType(token->AsString()); - if (!new_type) - return Result("invalid data type '" + token->AsString() + "' provided"); + token = tokenizer_->PeekNextToken(); + while (token->IsIdentifier()) { + if (token->AsString() == "FILL" || token->AsString() == "SERIES_FROM") + break; - fmt = MakeUnique(new_type.get()); - buffer->SetFormat(fmt.get()); - script_->RegisterType(std::move(new_type)); - } - script_->RegisterFormat(std::move(fmt)); - } else if (cmd == "FORMAT") { - token = tokenizer_->NextToken(); - if (!token->IsIdentifier()) - return Result("IMAGE FORMAT must be an identifier"); + tokenizer_->NextToken(); - auto type = script_->ParseType(token->AsString()); - if (!type) - return Result("invalid IMAGE FORMAT"); + if (token->AsString() == "DATA_TYPE") { + token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("IMAGE invalid data type"); - auto fmt = MakeUnique(type); - buffer->SetFormat(fmt.get()); - script_->RegisterFormat(std::move(fmt)); + auto type = script_->ParseType(token->AsString()); + std::unique_ptr fmt; + if (type != nullptr) { + fmt = MakeUnique(type); + buffer->SetFormat(fmt.get()); + } else { + auto new_type = ToType(token->AsString()); + if (!new_type) { + return Result("invalid data type '" + token->AsString() + + "' provided"); + } - token = tokenizer_->PeekNextToken(); - if (token->IsIdentifier() && token->AsString() == "MIP_LEVELS") { - tokenizer_->NextToken(); + fmt = MakeUnique(new_type.get()); + buffer->SetFormat(fmt.get()); + script_->RegisterType(std::move(new_type)); + } + script_->RegisterFormat(std::move(fmt)); + } else if (token->AsString() == "FORMAT") { + token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("IMAGE FORMAT must be an identifier"); + + auto type = script_->ParseType(token->AsString()); + if (!type) + return Result("invalid IMAGE FORMAT"); + + auto fmt = MakeUnique(type); + buffer->SetFormat(fmt.get()); + script_->RegisterFormat(std::move(fmt)); + } else if (token->AsString() == "MIP_LEVELS") { token = tokenizer_->NextToken(); if (!token->IsInteger()) return Result("invalid value for MIP_LEVELS"); buffer->SetMipLevels(token->AsUint32()); - } - } else { - return Result("unknown IMAGE command provided: " + cmd); - } - - token = tokenizer_->NextToken(); - if (!token->IsIdentifier()) { - return Result("IMAGE dimensionality must be an identifier: " + - token->ToOriginalString()); - } + } else if (token->AsString() == "DIM_1D") { + buffer->SetImageDimension(ImageDimension::k1D); + } else if (token->AsString() == "DIM_2D") { + buffer->SetImageDimension(ImageDimension::k2D); + } else if (token->AsString() == "DIM_3D") { + buffer->SetImageDimension(ImageDimension::k3D); + } else if (token->AsString() == "WIDTH") { + token = tokenizer_->NextToken(); + if (!token->IsInteger() || token->AsUint32() == 0) + return Result("expected positive IMAGE WIDTH"); - auto dim = StrToImageDimension(token->AsString()); - if (dim == ImageDimension::kUnknown) - return Result("unknown IMAGE dimensionality"); - buffer->SetImageDimension(dim); + buffer->SetWidth(token->AsUint32()); + width_set = true; + } else if (token->AsString() == "HEIGHT") { + token = tokenizer_->NextToken(); + if (!token->IsInteger() || token->AsUint32() == 0) + return Result("expected positive IMAGE HEIGHT"); - token = tokenizer_->NextToken(); - if (!token->IsIdentifier() || token->AsString() != "WIDTH") - return Result("expected IMAGE WIDTH"); + buffer->SetHeight(token->AsUint32()); + height_set = true; + } else if (token->AsString() == "DEPTH") { + token = tokenizer_->NextToken(); + if (!token->IsInteger() || token->AsUint32() == 0) + return Result("expected positive IMAGE DEPTH"); - // Parse image dimensions. - uint32_t width = 1; - uint32_t height = 1; - uint32_t depth = 1; - token = tokenizer_->NextToken(); - if (!token->IsInteger() || token->AsUint32() == 0) - return Result("expected positive IMAGE WIDTH"); - width = token->AsUint32(); - buffer->SetWidth(width); + buffer->SetDepth(token->AsUint32()); + depth_set = true; + } else if (token->AsString() == "SAMPLES") { + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("expected integer value for SAMPLES"); - if (dim == ImageDimension::k2D || dim == ImageDimension::k3D) { - token = tokenizer_->NextToken(); - if (!token->IsIdentifier() || token->AsString() != "HEIGHT") - return Result("expected IMAGE HEIGHT"); + const uint32_t samples = token->AsUint32(); + if (!IsValidSampleCount(samples)) + return Result("invalid sample count: " + token->ToOriginalString()); - token = tokenizer_->NextToken(); - if (!token->IsInteger() || token->AsUint32() == 0) - return Result("expected positive IMAGE HEIGHT"); - height = token->AsUint32(); - buffer->SetHeight(height); + buffer->SetSamples(samples); + } else { + return Result("unknown IMAGE command provided: " + + token->ToOriginalString()); + } + token = tokenizer_->PeekNextToken(); } - if (dim == ImageDimension::k3D) { - token = tokenizer_->NextToken(); - if (!token->IsIdentifier() || token->AsString() != "DEPTH") - return Result("expected IMAGE DEPTH"); + if (buffer->GetImageDimension() == ImageDimension::k3D && !depth_set) + return Result("expected IMAGE DEPTH"); - token = tokenizer_->NextToken(); - if (!token->IsInteger() || token->AsUint32() == 0) - return Result("expected positive IMAGE DEPTH"); - depth = token->AsUint32(); - buffer->SetDepth(depth); + if ((buffer->GetImageDimension() == ImageDimension::k3D || + buffer->GetImageDimension() == ImageDimension::k2D) && + !height_set) { + return Result("expected IMAGE HEIGHT"); } + if (!width_set) + return Result("expected IMAGE WIDTH"); - const uint32_t size_in_items = width * height * depth; + const uint32_t size_in_items = + buffer->GetWidth() * buffer->GetHeight() * buffer->GetDepth(); buffer->SetElementCount(size_in_items); // Parse initializers. diff --git a/src/amberscript/parser_buffer_test.cc b/src/amberscript/parser_buffer_test.cc index 491d0d43a..9286154db 100644 --- a/src/amberscript/parser_buffer_test.cc +++ b/src/amberscript/parser_buffer_test.cc @@ -602,6 +602,23 @@ TEST_F(AmberScriptParserTest, BufferFormat) { } } +TEST_F(AmberScriptParserTest, BufferSamples) { + std::string in = "BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES 2"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& buffers = script->GetBuffers(); + ASSERT_EQ(1U, buffers.size()); + + ASSERT_TRUE(buffers[0] != nullptr); + auto* buffer = buffers[0].get(); + EXPECT_EQ("my_buf", buffer->GetName()); + EXPECT_EQ(2u, buffer->GetSamples()); +} + struct BufferParseError { const char* in; const char* err; @@ -690,7 +707,11 @@ INSTANTIATE_TEST_SUITE_P( BufferParseError{"BUFFER my_buf DATA_TYPE int32 SIZE 5 FILL 5\nBUFFER " "my_buf DATA_TYPE int16 SIZE 5 FILL 2", // NOLINTNEXTLINE(whitespace/parens) - "2: duplicate buffer name provided"})); + "2: duplicate buffer name provided"}, + BufferParseError{"BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES 9", + "1: invalid sample count: 9"}, + BufferParseError{"BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES foo", + "1: expected integer value for SAMPLES"})); struct BufferData { const char* name; diff --git a/src/amberscript/parser_image_test.cc b/src/amberscript/parser_image_test.cc index 9d3731391..bbd98fbd8 100644 --- a/src/amberscript/parser_image_test.cc +++ b/src/amberscript/parser_image_test.cc @@ -116,7 +116,7 @@ IMAGE image DATA_TYPE uint32 DIM_WRONG Parser parser; Result r = parser.Parse(in); ASSERT_FALSE(r.IsSuccess()); - EXPECT_EQ("2: unknown IMAGE dimensionality", r.Error()); + EXPECT_EQ("2: unknown IMAGE command provided: DIM_WRONG", r.Error()); } TEST_F(AmberScriptParserTest, ImageDimensionalityInvalid2) { @@ -127,7 +127,7 @@ IMAGE image DATA_TYPE uint32 4 Parser parser; Result r = parser.Parse(in); ASSERT_FALSE(r.IsSuccess()); - EXPECT_EQ("2: IMAGE dimensionality must be an identifier: 4", r.Error()); + EXPECT_EQ("2: expected IMAGE WIDTH", r.Error()); } TEST_F(AmberScriptParserTest, ImageWidthMissing) { @@ -244,6 +244,47 @@ IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 EXPECT_EQ(12, buffer->ElementCount()); } +TEST_F(AmberScriptParserTest, Image2DMultiSample) { + std::string in = R"( +IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES 4 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()); + auto script = parser.GetScript(); + const auto& buffers = script->GetBuffers(); + ASSERT_EQ(1U, buffers.size()); + + ASSERT_TRUE(buffers[0] != nullptr); + EXPECT_EQ("image", buffers[0]->GetName()); + + auto* buffer = buffers[0].get(); + EXPECT_EQ(4, buffer->GetSamples()); +} + +TEST_F(AmberScriptParserTest, Image2DInvalidSampleValue) { + std::string in = R"( +IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES foo +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: expected integer value for SAMPLES", r.Error()); +} + +TEST_F(AmberScriptParserTest, Image2DInvalidSampleCount) { + std::string in = R"( +IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES 5 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: invalid sample count: 5", r.Error()); +} + TEST_F(AmberScriptParserTest, Image3D) { std::string in = R"( IMAGE image DATA_TYPE uint32 DIM_3D WIDTH 3 HEIGHT 4 DEPTH 5 diff --git a/src/buffer.h b/src/buffer.h index 347f876ac..967467ee1 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -206,6 +206,12 @@ class Buffer { /// Returns the number of mip levels. uint32_t GetMipLevels() const { return mip_levels_; } + /// Sets the number of samples. + void SetSamples(uint32_t samples) { samples_ = samples; } + + /// Returns the number of samples. + uint32_t GetSamples() const { return samples_; } + /// Returns a pointer to the internal storage of the buffer. std::vector* ValuePtr() { return &bytes_; } /// Returns a pointer to the internal storage of the buffer. @@ -257,6 +263,7 @@ class Buffer { uint32_t height_ = 1; uint32_t depth_ = 1; uint32_t mip_levels_ = 1; + uint32_t samples_ = 1; bool format_is_default_ = false; std::vector bytes_; Format* format_ = nullptr; diff --git a/src/vulkan/frame_buffer.cc b/src/vulkan/frame_buffer.cc index 816b2a4e9..47eb82de1 100644 --- a/src/vulkan/frame_buffer.cc +++ b/src/vulkan/frame_buffer.cc @@ -66,7 +66,7 @@ Result FrameBuffer::Initialize(VkRenderPass render_pass) { device_, *info->buffer->GetFormat(), VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_TYPE_2D, width_ << info->base_mip_level, height_ << info->base_mip_level, depth_, info->buffer->GetMipLevels(), - info->base_mip_level, 1u)); + info->base_mip_level, 1u, 1u)); Result r = color_images_.back()->Initialize( VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | @@ -89,7 +89,7 @@ Result FrameBuffer::Initialize(VkRenderPass render_pass) { depth_stencil_image_ = MakeUnique( device_, *depth_stencil_attachment_.buffer->GetFormat(), aspect, - VK_IMAGE_TYPE_2D, width_, height_, depth_, 1u, 0u, 1u); + VK_IMAGE_TYPE_2D, width_, height_, depth_, 1u, 0u, 1u, 1u); Result r = depth_stencil_image_->Initialize( VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | diff --git a/src/vulkan/image_descriptor.cc b/src/vulkan/image_descriptor.cc index c7f267ebc..e1e9535d8 100644 --- a/src/vulkan/image_descriptor.cc +++ b/src/vulkan/image_descriptor.cc @@ -95,8 +95,8 @@ Result ImageDescriptor::CreateResourceIfNeeded() { transfer_images_.emplace_back(MakeUnique( 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->GetMipLevels(), base_mip_level_, VK_REMAINING_MIP_LEVELS, + amber_buffer->GetSamples())); VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; diff --git a/src/vulkan/resource.cc b/src/vulkan/resource.cc index 320365fd7..a52df3d3c 100644 --- a/src/vulkan/resource.cc +++ b/src/vulkan/resource.cc @@ -173,7 +173,7 @@ void Resource::UpdateMemoryWithRawData(const std::vector& raw_data) { } void Resource::MemoryBarrier(CommandBuffer* command_buffer) { - // TODO(jaebaek): Current memory barrier is natively implemented. + // TODO(jaebaek): Current memory barrier is naively implemented. // Update it with the following access flags: // (r = read, w = write) // diff --git a/src/vulkan/transfer_image.cc b/src/vulkan/transfer_image.cc index d510b7922..60bd6fd89 100644 --- a/src/vulkan/transfer_image.cc +++ b/src/vulkan/transfer_image.cc @@ -43,6 +43,27 @@ const VkImageCreateInfo kDefaultImageInfo = { VK_IMAGE_LAYOUT_UNDEFINED, /* initialLayout */ }; +VkSampleCountFlagBits GetVkSampleCount(uint32_t samples) { + switch (samples) { + case 1u: + return VK_SAMPLE_COUNT_1_BIT; + case 2u: + return VK_SAMPLE_COUNT_2_BIT; + case 4u: + return VK_SAMPLE_COUNT_4_BIT; + case 8u: + return VK_SAMPLE_COUNT_8_BIT; + case 16u: + return VK_SAMPLE_COUNT_16_BIT; + case 32u: + return VK_SAMPLE_COUNT_32_BIT; + case 64u: + return VK_SAMPLE_COUNT_64_BIT; + } + + return VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM; +} + } // namespace TransferImage::TransferImage(Device* device, @@ -54,17 +75,20 @@ TransferImage::TransferImage(Device* device, uint32_t z, uint32_t mip_levels, uint32_t base_mip_level, - uint32_t used_mip_levels) + uint32_t used_mip_levels, + uint32_t samples) : Resource(device, x * y * z * format.SizeInBytes()), image_info_(kDefaultImageInfo), aspect_(aspect), mip_levels_(mip_levels), base_mip_level_(base_mip_level), - used_mip_levels_(used_mip_levels) { + used_mip_levels_(used_mip_levels), + samples_(samples) { image_info_.format = device_->GetVkFormat(format); image_info_.imageType = image_type; image_info_.extent = {x, y, z}; image_info_.mipLevels = mip_levels; + image_info_.samples = GetVkSampleCount(samples); } TransferImage::~TransferImage() { @@ -93,7 +117,7 @@ TransferImage::~TransferImage() { Result TransferImage::Initialize(VkImageUsageFlags usage) { if (image_ != VK_NULL_HANDLE) - return Result("Vulkan::TransferImage was already initalized"); + return Result("Vulkan::TransferImage was already initialized"); image_info_.usage = usage; @@ -222,6 +246,10 @@ void TransferImage::CopyToHost(CommandBuffer* command_buffer) { const VkImageAspectFlagBits aspects[] = {VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_ASPECT_STENCIL_BIT}; + // Copy operations don't support multisample images. + if (samples_ > 1) + return; + std::vector copy_regions; uint32_t last_mip_level = used_mip_levels_ == VK_REMAINING_MIP_LEVELS ? mip_levels_ @@ -243,6 +271,10 @@ void TransferImage::CopyToHost(CommandBuffer* command_buffer) { } void TransferImage::CopyToDevice(CommandBuffer* command_buffer) { + // Copy operations don't support multisample images. + if (samples_ > 1) + return; + const VkImageAspectFlagBits aspects[] = {VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_ASPECT_STENCIL_BIT}; diff --git a/src/vulkan/transfer_image.h b/src/vulkan/transfer_image.h index fbdfa2346..741ee3c60 100644 --- a/src/vulkan/transfer_image.h +++ b/src/vulkan/transfer_image.h @@ -38,7 +38,8 @@ class TransferImage : public Resource { uint32_t z, uint32_t mip_levels, uint32_t base_mip_level, - uint32_t used_mip_levels); + uint32_t used_mip_levels, + uint32_t samples); ~TransferImage() override; Result Initialize(VkImageUsageFlags usage); @@ -85,6 +86,7 @@ class TransferImage : public Resource { uint32_t mip_levels_; uint32_t base_mip_level_; uint32_t used_mip_levels_; + uint32_t samples_; }; } // namespace vulkan diff --git a/tests/cases/draw_storageimage_multisample.amber b/tests/cases/draw_storageimage_multisample.amber new file mode 100644 index 000000000..6ebebb521 --- /dev/null +++ b/tests/cases/draw_storageimage_multisample.amber @@ -0,0 +1,61 @@ +#!amber +# Copyright 2020 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. + +DEVICE_FEATURE shaderStorageImageMultisample + +SHADER compute compute_shader GLSL +#version 430 + +layout(local_size_x = 16, local_size_y = 16) in; +uniform layout(set=0, binding=0, rgba8) image2DMS texture; +uniform layout(set=0, binding=1, rgba8) image2D sample0; +uniform layout(set=0, binding=2, rgba8) image2D sample1; +uniform layout(set=0, binding=3, rgba8) image2D sample2; +uniform layout(set=0, binding=4, rgba8) image2D sample3; +void main() +{ + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + imageStore(texture, uv, 0, vec4(1, 0, 0, 1)); + imageStore(texture, uv, 1, vec4(0, 1, 0, 1)); + imageStore(texture, uv, 2, vec4(0, 0, 1, 1)); + imageStore(texture, uv, 3, vec4(1, 1, 0, 1)); + imageStore(sample0, uv, imageLoad(texture, uv, 3)); + imageStore(sample1, uv, imageLoad(texture, uv, 2)); + imageStore(sample2, uv, imageLoad(texture, uv, 1)); + imageStore(sample3, uv, imageLoad(texture, uv, 0)); +} +END + +IMAGE texture FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 256 HEIGHT 256 SAMPLES 4 +IMAGE sample0 FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 256 HEIGHT 256 +IMAGE sample1 FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 256 HEIGHT 256 +IMAGE sample2 FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 256 HEIGHT 256 +IMAGE sample3 FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 256 HEIGHT 256 + +PIPELINE compute pipeline + ATTACH compute_shader + BIND BUFFER texture AS storage_image DESCRIPTOR_SET 0 BINDING 0 + BIND BUFFER sample0 AS storage_image DESCRIPTOR_SET 0 BINDING 1 + BIND BUFFER sample1 AS storage_image DESCRIPTOR_SET 0 BINDING 2 + BIND BUFFER sample2 AS storage_image DESCRIPTOR_SET 0 BINDING 3 + BIND BUFFER sample3 AS storage_image DESCRIPTOR_SET 0 BINDING 4 +END + +RUN pipeline 16 16 1 + +EXPECT sample0 IDX 0 0 SIZE 256 256 EQ_RGBA 255 255 0 255 +EXPECT sample1 IDX 0 0 SIZE 256 256 EQ_RGBA 0 0 255 255 +EXPECT sample2 IDX 0 0 SIZE 256 256 EQ_RGBA 0 255 0 255 +EXPECT sample3 IDX 0 0 SIZE 256 256 EQ_RGBA 255 0 0 255 diff --git a/tests/run_tests.py b/tests/run_tests.py index d908998e6..4a6091557 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -84,6 +84,8 @@ "opencl_read_and_write_image3d_rgba32i.amber", "opencl_write_image.amber", "glsl_read_and_write_image3d_rgba32i.amber", + # shaderStorageImageMultisample feature not supported + "draw_storageimage_multisample.amber", ] OPENCL_CASES = [