From a6b0939fcdf9ff4979b03a898a9a44266128fef0 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 22 Mar 2022 14:35:26 -0700 Subject: [PATCH] Add support for instanced rendering and shader storage buffers. (#95) --- impeller/compiler/code_gen_template.h | 16 ++++-- impeller/compiler/reflector.cc | 45 +++++++++++++-- impeller/fixtures/BUILD.gn | 2 + impeller/fixtures/instanced_draw.frag | 11 ++++ impeller/fixtures/instanced_draw.vert | 24 ++++++++ .../renderer/backend/metal/render_pass_mtl.mm | 2 +- impeller/renderer/command.h | 1 + impeller/renderer/host_buffer.h | 23 ++++++++ impeller/renderer/renderer_unittests.cc | 57 +++++++++++++++++++ 9 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 impeller/fixtures/instanced_draw.frag create mode 100644 impeller/fixtures/instanced_draw.vert diff --git a/impeller/compiler/code_gen_template.h b/impeller/compiler/code_gen_template.h index 20afb9e2714f6..835c2b8cc4c28 100644 --- a/impeller/compiler/code_gen_template.h +++ b/impeller/compiler/code_gen_template.h @@ -44,16 +44,16 @@ struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader { }; // struct {{def.name}} (size {{def.byte_length}}) {% endfor %} {% endif %} -{% if length(uniform_buffers) > 0 %} +{% if length(buffers) > 0 %} // =========================================================================== - // Stage Uniforms ============================================================ + // Stage Uniform & Storage Buffers =========================================== // =========================================================================== -{% for uniform in uniform_buffers %} +{% for buffer in buffers %} - static constexpr auto kResource{{camel_case(uniform.name)}} = ShaderUniformSlot<{{uniform.name}}> { // {{uniform.name}} - "{{uniform.name}}", // name - {{uniform.msl_res_0}}u, // binding + static constexpr auto kResource{{camel_case(buffer.name)}} = ShaderUniformSlot<{{buffer.name}}> { // {{buffer.name}} + "{{buffer.name}}", // name + {{buffer.msl_res_0}}u, // binding }; {% endfor %} {% endif %} @@ -119,6 +119,10 @@ struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader { }; {% endif %} + // =========================================================================== + // Resource Binding Utilities ================================================ + // =========================================================================== + {% for proto in bind_prototypes %} /// {{proto.docstring}} static {{proto.return_type}} Bind{{proto.name}}({% for arg in proto.args %} diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc index b0f91d70e39ee..d7786dcc7d904 100644 --- a/impeller/compiler/reflector.cc +++ b/impeller/compiler/reflector.cc @@ -166,11 +166,27 @@ std::optional Reflector::GenerateTemplateArguments() const { const auto shader_resources = compiler_->get_shader_resources(); - if (auto uniform_buffers = ReflectResources(shader_resources.uniform_buffers); - uniform_buffers.has_value()) { - root["uniform_buffers"] = std::move(uniform_buffers.value()); - } else { - return std::nullopt; + // Uniform and storage buffers. + { + auto& buffers = root["buffers"] = nlohmann::json::array_t{}; + if (auto uniform_buffers_json = + ReflectResources(shader_resources.uniform_buffers); + uniform_buffers_json.has_value()) { + for (const auto& uniform_buffer : uniform_buffers_json.value()) { + buffers.emplace_back(std::move(uniform_buffer)); + } + } else { + return std::nullopt; + } + if (auto storage_buffers_json = + ReflectResources(shader_resources.storage_buffers); + storage_buffers_json.has_value()) { + for (const auto& uniform_buffer : storage_buffers_json.value()) { + buffers.emplace_back(std::move(uniform_buffer)); + } + } else { + return std::nullopt; + } } { @@ -742,6 +758,25 @@ std::vector Reflector::ReflectBindPrototypes( .argument_name = "view", }); } + for (const auto& storage_buffer : resources.storage_buffers) { + auto& proto = prototypes.emplace_back(BindPrototype{}); + proto.return_type = "bool"; + proto.name = ConvertToCamelCase(storage_buffer.name); + { + std::stringstream stream; + stream << "Bind storage buffer for resource named " << storage_buffer.name + << "."; + proto.docstring = stream.str(); + } + proto.args.push_back(BindPrototypeArgument{ + .type_name = "Command&", + .argument_name = "command", + }); + proto.args.push_back(BindPrototypeArgument{ + .type_name = "BufferView", + .argument_name = "view", + }); + } for (const auto& sampled_image : resources.sampled_images) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index db56ad66fb679..07bbc65499781 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -12,6 +12,8 @@ impeller_shaders("shader_fixtures") { shaders = [ "box_fade.vert", "box_fade.frag", + "instanced_draw.vert", + "instanced_draw.frag", "test_texture.vert", "test_texture.frag", ] diff --git a/impeller/fixtures/instanced_draw.frag b/impeller/fixtures/instanced_draw.frag new file mode 100644 index 0000000000000..ccad5b7d3e141 --- /dev/null +++ b/impeller/fixtures/instanced_draw.frag @@ -0,0 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +in vec4 v_color; + +out vec4 frag_color; + +void main() { + frag_color = v_color; +} diff --git a/impeller/fixtures/instanced_draw.vert b/impeller/fixtures/instanced_draw.vert new file mode 100644 index 0000000000000..ff35d518e7221 --- /dev/null +++ b/impeller/fixtures/instanced_draw.vert @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +readonly buffer InstanceInfo { + vec4 colors[]; +} instance_info; + +in vec2 vtx; + +out vec4 v_color; + +void main () { + gl_Position = frame_info.mvp * + vec4(vtx.x + 105.0 * gl_InstanceIndex, + vtx.y + 105.0 * gl_InstanceIndex, + 0.0, + 1.0); + v_color = instance_info.colors[gl_InstanceIndex]; +} diff --git a/impeller/renderer/backend/metal/render_pass_mtl.mm b/impeller/renderer/backend/metal/render_pass_mtl.mm index 937bca3dd0f75..ac55199e1ca36 100644 --- a/impeller/renderer/backend/metal/render_pass_mtl.mm +++ b/impeller/renderer/backend/metal/render_pass_mtl.mm @@ -482,7 +482,7 @@ static bool Bind(PassBindingsCache& pass, indexType:ToMTLIndexType(command.index_type) indexBuffer:mtl_index_buffer indexBufferOffset:command.index_buffer.range.offset - instanceCount:1u + instanceCount:command.instance_count baseVertex:command.base_vertex baseInstance:0u]; } diff --git a/impeller/renderer/command.h b/impeller/renderer/command.h index 3fdb0ee1716bd..d0299c4d5d31a 100644 --- a/impeller/renderer/command.h +++ b/impeller/renderer/command.h @@ -90,6 +90,7 @@ struct Command { /// If unset, no scissor is applied. /// std::optional scissor; + size_t instance_count = 1u; bool BindVertices(const VertexBuffer& buffer); diff --git a/impeller/renderer/host_buffer.h b/impeller/renderer/host_buffer.h index 82be86cbcba0f..91eba4fbcdb83 100644 --- a/impeller/renderer/host_buffer.h +++ b/impeller/renderer/host_buffer.h @@ -49,6 +49,29 @@ class HostBuffer final : public std::enable_shared_from_this, ); } + //---------------------------------------------------------------------------- + /// @brief Emplace storage buffer data onto the host buffer. Ensure that + /// backend specific uniform alignment requirements are respected. + /// + /// @param[in] uniform The storage buffer to emplace onto the buffer. + /// + /// @tparam StorageBufferType The type of the shader storage buffer. + /// + /// @return The buffer view. + /// + template < + class StorageBufferType, + class = std::enable_if_t>> + [[nodiscard]] BufferView EmplaceStorageBuffer( + const std::vector& buffer) { + const auto alignment = + std::max(alignof(StorageBufferType), DefaultUniformAlignment()); + return Emplace(buffer.data(), // buffer + buffer.size() * sizeof(StorageBufferType), // size + alignment // alignment + ); + } + //---------------------------------------------------------------------------- /// @brief Emplace non-uniform data (like contiguous vertices) onto the /// host buffer. diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index d4a3482416380..40fa47ecadf0d 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -6,6 +6,8 @@ #include "flutter/testing/testing.h" #include "impeller/fixtures/box_fade.frag.h" #include "impeller/fixtures/box_fade.vert.h" +#include "impeller/fixtures/instanced_draw.frag.h" +#include "impeller/fixtures/instanced_draw.vert.h" #include "impeller/fixtures/test_texture.frag.h" #include "impeller/fixtures/test_texture.vert.h" #include "impeller/geometry/path_builder.h" @@ -270,5 +272,60 @@ TEST_F(RendererTest, CanRenderToTexture) { ASSERT_TRUE(r2t_pass->EncodeCommands(*context->GetTransientsAllocator())); } +TEST_F(RendererTest, CanRenderInstanced) { + using VS = InstancedDrawVertexShader; + using FS = InstancedDrawFragmentShader; + + VertexBufferBuilder builder; + + ASSERT_TRUE( + Tessellator{}.Tessellate(FillType::kPositive, + PathBuilder{} + .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) + .TakePath() + .CreatePolyline(), + [&builder](Point vtx) { + VS::PerVertexData data; + data.vtx = vtx; + builder.AppendVertex(data); + })); + + auto pipeline = + GetContext() + ->GetPipelineLibrary() + ->GetRenderPipeline( + PipelineBuilder::MakeDefaultPipelineDescriptor( + *GetContext()) + ->SetSampleCount(SampleCount::kCount4)) + .get(); + ASSERT_TRUE(pipeline && pipeline->IsValid()); + + Command cmd; + cmd.pipeline = pipeline; + cmd.label = "InstancedDraw"; + + static constexpr size_t kInstancesCount = 5u; + std::vector instances; + for (size_t i = 0; i < kInstancesCount; i++) { + VS::InstanceInfo info; + info.colors = Color::Random(); + instances.emplace_back(info); + } + + ASSERT_TRUE(OpenPlaygroundHere([&](RenderPass& pass) -> bool { + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + VS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + VS::BindInstanceInfo( + cmd, pass.GetTransientsBuffer().EmplaceStorageBuffer(instances)); + cmd.BindVertices(builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + cmd.instance_count = kInstancesCount; + pass.AddCommand(cmd); + return true; + })); +} + } // namespace testing } // namespace impeller