| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/lib/escher/flatland/rectangle_compositor.h" |
| |
| #include "src/ui/lib/escher/impl/naive_image.h" |
| #include "src/ui/lib/escher/mesh/indexed_triangle_mesh_upload.h" |
| #include "src/ui/lib/escher/mesh/tessellation.h" |
| #include "src/ui/lib/escher/renderer/render_funcs.h" |
| #include "src/ui/lib/escher/util/image_utils.h" |
| #include "src/ui/lib/escher/vk/shader_program.h" |
| #include "src/ui/lib/escher/vk/texture.h" |
| |
| namespace escher { |
| const vk::ImageUsageFlags RectangleCompositor::kRenderTargetUsageFlags = |
| vk::ImageUsageFlagBits::eColorAttachment; |
| const vk::ImageUsageFlags RectangleCompositor::kTextureUsageFlags = |
| vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled; |
| |
| namespace { |
| |
| static constexpr uint32_t kTransientTargetAttachmentIndex = 0; |
| static constexpr uint32_t kOutputTargetAttachmentIndex = 1; |
| |
| // Helper function which factors out common code from the two InitRenderPassInfo() variants. |
| static void InitRenderPassInfoHelper(RenderPassInfo* rp, |
| const RenderPassInfo::AttachmentInfo& transient_info, |
| const RenderPassInfo::AttachmentInfo& output_info, |
| const RenderPassInfo::AttachmentInfo& depth_stencil_info) { |
| FX_DCHECK(output_info.sample_count == 1); |
| |
| rp->color_attachment_infos[kTransientTargetAttachmentIndex] = transient_info; |
| rp->color_attachment_infos[kOutputTargetAttachmentIndex] = output_info; |
| rp->depth_stencil_attachment_info = depth_stencil_info; |
| |
| { |
| // We have one transient attachment, and one output attachment. |
| rp->num_color_attachments = 2; |
| |
| // Clear the intermediate attachment. We don't need to store it. |
| rp->clear_attachments = 1u << kTransientTargetAttachmentIndex; |
| |
| // Clear and store the output color attachment 1. |
| rp->clear_attachments |= 1u << kOutputTargetAttachmentIndex; |
| rp->store_attachments |= 1u << kOutputTargetAttachmentIndex; |
| |
| // Standard flags for a depth-testing render-pass that needs to first clear |
| // the depth image. |
| rp->op_flags = RenderPassInfo::kClearDepthStencilOp | RenderPassInfo::kOptimalColorLayoutOp | |
| RenderPassInfo::kOptimalDepthStencilLayoutOp; |
| FX_DCHECK(depth_stencil_info.format != vk::Format::eUndefined); |
| |
| rp->clear_color[kTransientTargetAttachmentIndex].setFloat32({0.f, 0.f, 0.f, 0.f}); |
| rp->clear_color[kOutputTargetAttachmentIndex].setFloat32({0.f, 0.f, 0.f, 0.f}); |
| } |
| |
| // This is the subpass used to render the renderables. They render to a |
| // transient image. |
| RenderPassInfo::Subpass subpass = {.color_attachments = {kTransientTargetAttachmentIndex}, |
| .input_attachments = {}, |
| .resolve_attachments = {}, |
| .num_color_attachments = 1, |
| .num_input_attachments = 0, |
| .num_resolve_attachments = 0}; |
| |
| // This is the subpass used to perform color conversion. The transient attachment |
| // from subpass1 becomes the input attachment here, and then this subpass renders |
| // out to the render target. |
| RenderPassInfo::Subpass subpass2 = {.color_attachments = {kOutputTargetAttachmentIndex}, |
| .input_attachments = {kTransientTargetAttachmentIndex}, |
| .resolve_attachments = {}, |
| .num_color_attachments = 1, |
| .num_input_attachments = 1, |
| .num_resolve_attachments = 0}; |
| |
| rp->subpasses.push_back(subpass); |
| rp->subpasses.push_back(subpass2); |
| |
| // Make null the remaining color attachment slots we are not using. |
| for (size_t i = rp->num_color_attachments; i < VulkanLimits::kNumColorAttachments; ++i) { |
| rp->color_attachment_infos[i] = {}; |
| rp->color_attachments[i] = nullptr; |
| } |
| } |
| |
| // We need a render pass with two subpasses in order to apply color conversion. |
| // The first subpass renders each of the renderables to a transient framebuffer, |
| // and the second subpass reads in those transient values as input, and is used to |
| // compute color conversion as a post processing effect over the entire output |
| // framebuffer. Since color-conversion doesn't require knowledge of adjacent |
| // pixels, subpasses are a relatively straightforward way to handle it. |
| bool SetupColorConversionDualPass(RenderPassInfo* rp, vk::Rect2D render_area, |
| const ImagePtr& transient_image, const ImagePtr& output_image, |
| const TexturePtr& depth_texture) { |
| FX_DCHECK(output_image->info().sample_count == 1); |
| rp->render_area = render_area; |
| |
| RenderPassInfo::AttachmentInfo transient_info, output_info; |
| RenderPassInfo::AttachmentInfo depth_stencil_info; |
| if (output_image) { |
| if (!output_image->is_swapchain_image()) { |
| FX_LOGS(ERROR) << "RenderPassInfo::InitRenderPassInfo(): Output image doesn't have valid " |
| "swapchain layout."; |
| return false; |
| } |
| if (output_image->swapchain_layout() != output_image->layout()) { |
| FX_LOGS(ERROR) << "RenderPassInfo::InitRenderPassInfo(): Current layout of output image " |
| "does not match its swapchain layout."; |
| return false; |
| } |
| transient_info.InitFromImage(transient_image); |
| output_info.InitFromImage(output_image); |
| } |
| |
| depth_stencil_info.InitFromImage(depth_texture->image()); |
| |
| InitRenderPassInfoHelper(rp, transient_info, output_info, depth_stencil_info); |
| |
| ImageViewPtr transient_image_view = ImageView::New(transient_image); |
| ImageViewPtr output_image_view = ImageView::New(output_image); |
| rp->color_attachments[kTransientTargetAttachmentIndex] = std::move(transient_image_view); |
| rp->color_attachments[kOutputTargetAttachmentIndex] = std::move(output_image_view); |
| rp->depth_stencil_attachment = depth_texture; |
| return true; |
| } |
| |
| vec4 GetPremultipliedRgba(vec4 rgba) { return vec4(vec3(rgba) * rgba.a, rgba.a); } |
| |
| // Draws a single rectangle at a particular depth value, z. |
| void DrawSingle(CommandBuffer* cmd_buf, const ShaderProgramPtr& program, |
| const Rectangle2D& rectangle, const Texture* texture, const glm::vec4& color, |
| float z) { |
| TRACE_DURATION("gfx", "RectangleCompositor::DrawSingle"); |
| |
| // Set the shader program to be used. |
| const SamplerPtr& sampler = texture->sampler()->is_immutable() ? texture->sampler() : nullptr; |
| cmd_buf->SetShaderProgram(program, sampler); |
| |
| // Bind texture to use in the fragment shader. |
| cmd_buf->BindTexture(/*set*/ 0, /*binding*/ 0, texture); |
| |
| // Struct to store all the push constant data in the vertex shader |
| // so we only need to make a single call to PushConstants(). |
| struct VertexShaderPushConstants { |
| alignas(16) vec3 origin; |
| alignas(8) vec2 extent; |
| alignas(8) std::array<vec2, 4> uvs; |
| }; |
| |
| // Set up the push constants struct with data from the renderable and z value. |
| VertexShaderPushConstants constants = { |
| .origin = vec3(rectangle.origin, z), |
| .extent = rectangle.extent, |
| .uvs = rectangle.clockwise_uvs, |
| }; |
| |
| // We offset by 16U to account for the fact that the previous call to |
| // PushConstants() for the batch-level bounds was a glm::vec3, which |
| // takes up 16 bytes with padding in the vertex shader. |
| cmd_buf->PushConstants(constants, /*offset*/ 16U); |
| |
| // We make one more call to PushConstants() to push the color to the |
| // fragment shader. This is so that the data aligns with the push constant |
| // range for the fragment shader only, otherwise it would overlap the ranges |
| // for both the vertex and fragment shaders. |
| cmd_buf->PushConstants(GetPremultipliedRgba(color), /*offset*/ 80U); |
| |
| // In Vulkan, YUV textures don't have a color space defined by the format. The OETF (Opto |
| // Electrical Transfer Function) for BT.709 is closely approximated by using power of 2 for the |
| // RGB components of the sampled texture in the fragment shader. We make another call to |
| // PushConstants() to push this gamma power value. |
| cmd_buf->PushConstants(texture->is_yuv_format() ? 2.f : 1.f, /*offset*/ 96U); |
| |
| // Draw two triangles. The vertex shader knows how to use the gl_VertexIndex |
| // of each vertex to compute the appropriate position and UV values. |
| cmd_buf->Draw(/*vertex_count*/ 6); |
| } |
| |
| // Renders the batch of provided rectangles using the provided shader program. |
| // Renderables are separated into opaque and translucent groups. The opaque |
| // renderables are rendered from front-to-back while the translucent renderables |
| // are rendered from back-to-front. |
| void TraverseBatch(CommandBuffer* cmd_buf, vec3 bounds, ShaderProgramPtr program, |
| const std::vector<Rectangle2D>& rectangles, |
| const std::vector<const TexturePtr>& textures, |
| const std::vector<RectangleCompositor::ColorData>& color_data) { |
| TRACE_DURATION("gfx", "RectangleCompositor::TraverseBatch"); |
| int64_t num_renderables = static_cast<int64_t>(rectangles.size()); |
| |
| // Push the bounds as a constant for all renderables to be used in the vertex shader. |
| cmd_buf->PushConstants(bounds); |
| |
| // Opaque, front to back. |
| { |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| cmd_buf->SetDepthTestAndWrite(true, true); |
| |
| float z = 1.f; |
| for (int64_t i = num_renderables - 1; i >= 0; i--) { |
| if (color_data[i].is_opaque) { |
| DrawSingle(cmd_buf, program, rectangles[i], textures[i].get(), color_data[i].color, z); |
| } |
| z += 1.f; |
| } |
| } |
| |
| // Translucent, back to front. |
| { |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kTranslucent); |
| cmd_buf->SetDepthTestAndWrite(true, false); |
| float z = static_cast<float>(rectangles.size()); |
| for (int64_t i = 0; i < num_renderables; i++) { |
| if (!color_data[i].is_opaque) { |
| DrawSingle(cmd_buf, program, rectangles[i], textures[i].get(), color_data[i].color, z); |
| } |
| z -= 1.f; |
| } |
| } |
| } |
| |
| void ApplyColorConversion(CommandBuffer* cmd_buf, ShaderProgramPtr program, |
| ImageViewPtr input_attachment, |
| const ColorConversionParams& color_conversion_params) { |
| TRACE_DURATION("gfx", "RectangleCompositor::ApplyColorConversion"); |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| cmd_buf->SetDepthTestAndWrite(false, false); |
| |
| cmd_buf->SetShaderProgram(program, nullptr); |
| |
| cmd_buf->BindInputAttachment(/*set*/ 0, /*binding*/ 0, input_attachment); |
| |
| cmd_buf->PushConstants(color_conversion_params); |
| |
| // Draw one triangle. The vertex shader knows how to use the gl_VertexIndex |
| // of each vertex to compute the appropriate position. |
| cmd_buf->Draw(/*vertex_count*/ 3); |
| } |
| |
| } // anonymous namespace |
| |
| // RectangleCompositor constructor. Initializes the shader program and allocates |
| // GPU buffers to store mesh data. |
| RectangleCompositor::RectangleCompositor(EscherWeakPtr escher) |
| : escher_(escher), |
| standard_program_(escher->GetProgram(kFlatlandStandardProgram)), |
| color_conversion_program_(escher->GetProgram(kFlatlandColorConversionProgram)) {} |
| |
| // DrawBatch generates the Vulkan data needed to render the batch (e.g. renderpass, |
| // bounds, etc) and calls |TraverseBatch| which iterates over the renderables and |
| // submits them for rendering. |
| void RectangleCompositor::DrawBatch(CommandBuffer* cmd_buf, |
| const std::vector<Rectangle2D>& rectangles, |
| const std::vector<const TexturePtr>& textures, |
| const std::vector<ColorData>& color_data, |
| const ImagePtr& output_image, const TexturePtr& depth_buffer, |
| bool apply_color_conversion) { |
| // TODO(fxbug.dev/43278): Add custom clear colors. We could either pass in another parameter to |
| // this function or try to embed clear-data into the existing api. For example, one could |
| // check to see if the back rectangle is fullscreen and solid-color, in which case we can |
| // treat it as a clear instead of rendering it as a renderable. |
| FX_CHECK(cmd_buf && output_image && depth_buffer); |
| |
| // Inputs need to be the same length. |
| FX_CHECK(rectangles.size() == textures.size()); |
| FX_CHECK(rectangles.size() == color_data.size()); |
| |
| // Initialize the render pass. |
| RenderPassInfo render_pass; |
| vk::Rect2D render_area = {{0, 0}, {output_image->width(), output_image->height()}}; |
| |
| // Construct the bounds that are used in the vertex shader to convert the |
| // renderable positions into normalized device coordinates (NDC). The width |
| // and height are divided by 2 to pre-optimize the shift that happens in the |
| // shader which realigns the NDC coordinates so that (0,0) is in the center |
| // instead of in the top-left-hand corner. |
| vec3 bounds(static_cast<float>(output_image->width() * 0.5), |
| static_cast<float>(output_image->height() * 0.5), rectangles.size()); |
| |
| // If we don't have any color conversion data, stick to a single subpass. |
| if (!apply_color_conversion) { |
| // Setup a standard 1-pass renderpass where we render directly into the output image. |
| if (!RenderPassInfo::InitRenderPassInfo(&render_pass, render_area, output_image, |
| depth_buffer)) { |
| FX_LOGS(ERROR) << "RectangleCompositor::DrawBatch(): RenderPassInfo initialization failed. " |
| "Exiting."; |
| return; |
| } |
| |
| // Start the render pass. |
| cmd_buf->BeginRenderPass(render_pass); |
| |
| // Iterate over all the renderables and draw them. |
| TraverseBatch(cmd_buf, bounds, standard_program_, rectangles, textures, color_data); |
| |
| // End the render pass. |
| cmd_buf->EndRenderPass(); |
| |
| } |
| // Here we'll need to setup the dual pass system. |
| else { |
| auto transient_image = CreateOrFindTransientImage(output_image); |
| |
| // Setup a 2-pass render pass where we first render into an intermediate buffer (not really: |
| // we try to use a transient buffer to avoid flushing memory from GPU caches to GPU-external |
| // memory) and then use that as an input attachment for the output pass, where we finally |
| // apply color correction. |
| if (!SetupColorConversionDualPass(&render_pass, render_area, transient_image, output_image, |
| depth_buffer)) { |
| FX_LOGS(ERROR) << "RectangleCompositor::DrawBatch(): RenderPassInfo initialization failed. " |
| "Exiting."; |
| return; |
| } |
| |
| if (transient_image->layout() != vk::ImageLayout::eColorAttachmentOptimal) { |
| cmd_buf->impl()->TransitionImageLayout(transient_image, vk::ImageLayout::eUndefined, |
| vk::ImageLayout::eColorAttachmentOptimal); |
| } |
| |
| // Start the render pass. |
| cmd_buf->BeginRenderPass(render_pass); |
| |
| // Iterate over all the renderables and draw them. |
| TraverseBatch(cmd_buf, bounds, standard_program_, rectangles, textures, color_data); |
| |
| cmd_buf->NextSubpass(); |
| |
| ApplyColorConversion(cmd_buf, color_conversion_program_, |
| render_pass.color_attachments[kTransientTargetAttachmentIndex], |
| color_conversion_params_); |
| |
| // End the render pass. |
| cmd_buf->EndRenderPass(); |
| } |
| } |
| |
| // TODO(fxbug.dev/94252): It doesn't seem like all platforms actually support transient images. |
| // So this is going to be a regular image for now. |
| ImagePtr RectangleCompositor::CreateOrFindTransientImage(const ImagePtr& image) { |
| auto itr = transient_image_map_.find(image->info()); |
| if (itr != transient_image_map_.end()) { |
| return itr->second; |
| } |
| |
| ImageInfo info; |
| info.format = image->info().format; |
| info.width = image->info().width; |
| info.height = image->info().height; |
| info.sample_count = 1; |
| info.usage = vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eColorAttachment; |
| info.color_space = image->info().color_space; |
| info.memory_flags = vk::MemoryPropertyFlagBits::eDeviceLocal; |
| if (image->use_protected_memory()) { |
| info.memory_flags |= vk::MemoryPropertyFlagBits::eProtected; |
| } |
| |
| vk::Image vk_image = |
| image_utils::CreateVkImage(escher_->vk_device(), info, vk::ImageLayout::eUndefined); |
| |
| auto allocator = escher_->gpu_allocator(); |
| auto mem_requirements = escher_->vk_device().getImageMemoryRequirements(vk_image); |
| auto memory = allocator->AllocateMemory(mem_requirements, info.memory_flags); |
| auto result = impl::NaiveImage::AdoptVkImage(escher_->resource_recycler(), info, vk_image, memory, |
| vk::ImageLayout::eUndefined); |
| |
| transient_image_map_[image->info()] = result; |
| return result; |
| } |
| |
| vk::ImageCreateInfo RectangleCompositor::GetDefaultImageConstraints(const vk::Format& vk_format, |
| vk::ImageUsageFlags usage) { |
| vk::ImageCreateInfo create_info; |
| create_info.imageType = vk::ImageType::e2D; |
| create_info.extent = vk::Extent3D{1, 1, 1}; |
| create_info.flags = {}; |
| create_info.format = vk_format; |
| create_info.mipLevels = 1; |
| create_info.arrayLayers = 1; |
| create_info.samples = vk::SampleCountFlagBits::e1; |
| create_info.tiling = vk::ImageTiling::eOptimal; |
| create_info.usage = usage; |
| create_info.sharingMode = vk::SharingMode::eExclusive; |
| create_info.initialLayout = vk::ImageLayout::eUndefined; |
| return create_info; |
| } |
| |
| } // namespace escher |