blob: 805d4d3d48f8c0596f07693275e3a79951f84a6d [file] [log] [blame]
// Copyright 2018 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/paper/paper_renderer.h"
#include <glm/gtc/matrix_access.hpp>
#include <vulkan/vulkan.hpp>
// TODO(fxbug.dev/7244): possibly delete. See comment below about NUM_CLIP_PLANES.
#include "src/ui/lib/escher/debug/debug_font.h"
#include "src/ui/lib/escher/debug/debug_rects.h"
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/mesh/tessellation.h"
#include "src/ui/lib/escher/paper/paper_render_queue_context.h"
#include "src/ui/lib/escher/paper/paper_renderer_static_config.h"
#include "src/ui/lib/escher/paper/paper_scene.h"
#include "src/ui/lib/escher/paper/paper_shader_structs.h"
#include "src/ui/lib/escher/renderer/batch_gpu_uploader.h"
#include "src/ui/lib/escher/renderer/render_funcs.h"
#include "src/ui/lib/escher/scene/object.h"
// TODO(fxbug.dev/44894): try to avoid including an "impl" file.
#include "src/ui/lib/escher/third_party/granite/vk/command_buffer_pipeline_state.h"
#include "src/ui/lib/escher/third_party/granite/vk/render_pass.h"
#include "src/ui/lib/escher/util/string_utils.h"
#include "src/ui/lib/escher/util/trace_macros.h"
#include "src/ui/lib/escher/vk/command_buffer.h"
#include "src/ui/lib/escher/vk/image.h"
#include "src/ui/lib/escher/vk/impl/render_pass_cache.h"
#include "src/ui/lib/escher/vk/pipeline_builder.h"
#include "src/ui/lib/escher/vk/render_pass_info.h"
#include "src/ui/lib/escher/vk/shader_program.h"
#include "src/ui/lib/escher/vk/texture.h"
namespace escher {
PaperRendererPtr PaperRenderer::New(EscherWeakPtr escher, const PaperRendererConfig& config) {
return fxl::AdoptRef(new PaperRenderer(std::move(escher), config));
}
PaperRenderer::PaperRenderer(EscherWeakPtr weak_escher, const PaperRendererConfig& config)
: escher_(weak_escher),
context_(weak_escher->vulkan_context()),
config_(config),
draw_call_factory_(weak_escher, config),
shape_cache_(std::move(weak_escher), config) {
FX_DCHECK(config.num_depth_buffers > 0);
depth_buffers_.resize(config.num_depth_buffers);
msaa_buffers_.resize(config.num_depth_buffers);
supports_transient_attachments_ =
(0 != escher::impl::GetMemoryTypeIndices(escher_->vk_physical_device(), 0xffffffff,
vk::MemoryPropertyFlagBits::eLazilyAllocated |
vk::MemoryPropertyFlagBits::eDeviceLocal));
supports_protected_transient_attachments_ =
(0 != escher::impl::GetMemoryTypeIndices(escher_->vk_physical_device(), 0xffffffff,
vk::MemoryPropertyFlagBits::eLazilyAllocated |
vk::MemoryPropertyFlagBits::eDeviceLocal |
vk::MemoryPropertyFlagBits::eProtected));
}
PaperRenderer::~PaperRenderer() = default;
PaperRenderer::FrameData::FrameData(const FramePtr& frame_in,
std::shared_ptr<BatchGpuUploader> gpu_uploader_in,
const PaperScenePtr& scene_in, const ImagePtr& output_image_in,
std::pair<TexturePtr, TexturePtr> depth_and_msaa_textures,
const std::vector<Camera>& cameras_in)
: frame(frame_in),
output_image(output_image_in),
depth_texture(depth_and_msaa_textures.first),
msaa_texture(depth_and_msaa_textures.second),
gpu_uploader(gpu_uploader_in),
scene(scene_in) {
// Scale the camera viewports to pixel coordinates in the output framebuffer.
for (auto& cam : cameras_in) {
vk::Rect2D rect = cam.viewport().vk_rect_2d(output_image->width(), output_image->height());
vk::Viewport viewport(static_cast<float>(rect.offset.x), static_cast<float>(rect.offset.y),
static_cast<float>(rect.extent.width),
static_cast<float>(rect.extent.height), 0, 1);
UniformBinding binding;
CameraEye eye = CameraEye::kLeft;
if (auto buffer = cam.latched_pose_buffer()) {
// The camera has a latched pose-buffer, so we use it to obtain a
// view-projection matrix in the shader. We pass the eye_index as a
// push-constant to obtain the correct matrix.
frame->cmds()->KeepAlive(buffer);
binding.descriptor_set_index = PaperShaderLatchedPoseBuffer::kDescriptorSet;
binding.binding_index = PaperShaderLatchedPoseBuffer::kDescriptorBinding;
binding.buffer = buffer.get();
binding.offset = 0;
binding.size = sizeof(PaperShaderLatchedPoseBuffer);
eye = cam.latched_camera_eye();
} else {
// The camera has no latched pose-buffer, so allocate/populate uniform
// data with the same layout, based on the camera's projection/transform
// matrices.
auto pair = NewPaperShaderUniformBinding<PaperShaderLatchedPoseBuffer>(frame);
pair.first->vp_matrix[0] = cam.projection() * cam.transform();
pair.first->vp_matrix[1] = cam.projection() * cam.transform();
binding = pair.second;
}
cameras.push_back({.binding = binding,
.rect = rect,
.viewport = viewport,
.eye_index = (eye == CameraEye::kLeft ? 0U : 1U)});
}
// Generate a UniformBinding for global scene data (e.g. ambient lighting).
{
auto writable_binding = NewPaperShaderUniformBinding<PaperShaderSceneData>(frame);
writable_binding.first->ambient_light_color = scene->ambient_light.color;
scene_uniform_bindings.push_back(writable_binding.second);
}
// Generate a UniformBinding containing data for all point lights, if any.
auto num_lights = scene->num_point_lights();
if (num_lights > 0) {
auto writable_binding = NewPaperShaderUniformBinding<PaperShaderPointLight>(frame, num_lights);
auto* point_lights = writable_binding.first;
for (size_t i = 0; i < num_lights; ++i) {
const PaperPointLight& light = scene->point_lights[i];
point_lights[i].position = vec4(light.position, 1);
point_lights[i].color = vec4(light.color, 1);
point_lights[i].falloff = light.falloff;
}
scene_uniform_bindings.push_back(writable_binding.second);
}
}
PaperRenderer::FrameData::~FrameData() = default;
void PaperRenderer::SetConfig(const PaperRendererConfig& config) {
FX_DCHECK(!frame_data_) << "Illegal call to SetConfig() during a frame.";
FX_DCHECK(SupportsShadowType(config.shadow_type))
<< "Unsupported shadow type: " << config.shadow_type;
FX_DCHECK(config.num_depth_buffers > 0);
FX_DCHECK(config.msaa_sample_count == 1 || config.msaa_sample_count == 2 ||
config.msaa_sample_count == 4);
const auto& supported_sample_counts = escher()->device()->caps().msaa_sample_counts;
if (supported_sample_counts.find(config.msaa_sample_count) == supported_sample_counts.end()) {
FX_LOGS(ERROR) << "PaperRenderer: MSAA sample count ("
<< static_cast<uint32_t>(config.msaa_sample_count)
<< ") is not supported on this device. SetConfig failed.";
return;
}
if (escher()
->device()
->caps()
.GetMatchingDepthStencilFormat({config.depth_stencil_format})
.result != vk::Result::eSuccess) {
FX_LOGS(ERROR) << "PaperRenderer: Depth stencil format ("
<< vk::to_string(config.depth_stencil_format)
<< ") is not supported on this device. SetConfig failed.";
return;
}
if (config.msaa_sample_count != config_.msaa_sample_count) {
FX_VLOGS(1) << "PaperRenderer: MSAA sample count set to: " << config.msaa_sample_count
<< " (was: " << config_.msaa_sample_count << ")";
depth_buffers_.clear();
msaa_buffers_.clear();
}
if (config.depth_stencil_format != config_.depth_stencil_format) {
FX_VLOGS(1) << "PaperRenderer: depth_stencil_format set to: "
<< vk::to_string(config.depth_stencil_format)
<< " (was: " << vk::to_string(config_.depth_stencil_format) << ")";
depth_buffers_.clear();
}
if (config.num_depth_buffers != config_.num_depth_buffers) {
FX_VLOGS(1) << "PaperRenderer: num_depth_buffers set to: " << config.num_depth_buffers
<< " (was: " << config_.num_depth_buffers << ")";
}
// This is done here (instead of the if-statement above) because there may
// have been a change to the MSAA sample count.
depth_buffers_.resize(config.num_depth_buffers);
msaa_buffers_.resize(config.num_depth_buffers);
config_ = config;
draw_call_factory_.SetConfig(config_);
shape_cache_.SetConfig(config_);
}
bool PaperRenderer::SupportsShadowType(PaperRendererShadowType shadow_type) const {
return shadow_type == PaperRendererShadowType::kNone ||
shadow_type == PaperRendererShadowType::kShadowVolume;
}
void PaperRenderer::BeginFrame(const FramePtr& frame, std::shared_ptr<BatchGpuUploader> uploader,
const PaperScenePtr& scene, const std::vector<Camera>& cameras,
const ImagePtr& output_image) {
TRACE_DURATION("gfx", "PaperRenderer::BeginFrame");
FX_DCHECK(!frame_data_) << "already in a frame.";
FX_DCHECK(frame && uploader && scene && !cameras.empty() && output_image);
auto index = frame->frame_number() % depth_buffers_.size();
TexturePtr& depth_texture = depth_buffers_[index];
TexturePtr& msaa_texture = msaa_buffers_[index];
const bool use_transient_attachment = frame->use_protected_memory()
? supports_protected_transient_attachments_
: supports_transient_attachments_;
RenderFuncs::ObtainDepthAndMsaaTextures(escher(), frame, output_image->width(),
output_image->height(), config_.msaa_sample_count,
use_transient_attachment, config_.depth_stencil_format,
output_image->format(), depth_texture, msaa_texture);
frame_data_ = std::make_unique<FrameData>(
frame, std::move(uploader), scene, output_image,
std::pair<TexturePtr, TexturePtr>(depth_texture, msaa_texture), cameras);
shape_cache_.BeginFrame(frame_data_->gpu_uploader.get(), frame->frame_number());
{
// As described in the header file, we use the first camera's transform for
// the purpose of depth-sorting.
mat4 camera_transform = cameras[0].transform();
// A camera's transform doesn't move the camera; it is applied to the rest
// of the scene to "move it away from the camera". Therefore, the camera's
// position in the scene can be obtained by inverting it and applying it to
// the origin, or equivalently by inverting the transform and taking the
// rightmost (translation) column.
vec3 camera_pos(glm::column(glm::inverse(camera_transform), 3));
// The camera points down the negative-Z axis, so its world-space direction
// can be obtained by applying the camera transform to the direction vector
// [0, 0, -1, 0] (remembering that directions vectors have a w-coord of 0,
// vs. 1 for position vectors). This is equivalent to taking the negated
// third column of the transform.
vec3 camera_dir = -vec3(glm::column(camera_transform, 2));
draw_call_factory_.BeginFrame(frame, frame_data_->gpu_uploader.get(), scene.get(),
&transform_stack_, &render_queue_, &shape_cache_, camera_pos,
camera_dir);
}
}
void PaperRenderer::FinalizeFrame() {
TRACE_DURATION("gfx", "PaperRenderer::FinalizeFrame");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized && frame_data_->gpu_uploader);
// We may need to lazily instantiate |debug_font|, or delete it. If the former, this needs to be
// done before we submit the GPU uploader's tasks.
// TODO(fxbug.dev/7313): Clean up lazy instantiation. Right now, DebugFont and DebugRects are
// created/destroyed from frame-to-frame.
if (config_.debug_frame_number) {
DrawDebugText(std::to_string(frame_data_->frame->frame_number()), {10, 10}, 4);
}
if (!frame_data_->texts.empty()) {
if (!debug_font_) {
debug_font_ = DebugFont::New(frame_data_->gpu_uploader.get(), escher()->image_cache());
}
} else {
debug_font_.reset();
}
if (!frame_data_->lines.empty()) {
if (!debug_lines_) {
debug_lines_ = DebugRects::New(frame_data_->gpu_uploader.get(), escher()->image_cache());
}
} else {
debug_lines_.reset();
}
// At this point, all uploads are finished, and no Vulkan commands that depend on these
// uploads have yet been generated. After this point, no additional uploads are allowed.
frame_data_->scene_finalized = true;
frame_data_->gpu_uploader.reset();
}
void PaperRenderer::EndFrame(const std::vector<SemaphorePtr>& upload_wait_semaphores) {
TRACE_DURATION("gfx", "PaperRenderer::EndFrame");
FX_DCHECK(frame_data_);
FX_DCHECK(frame_data_->scene_finalized && !frame_data_->gpu_uploader);
for (const SemaphorePtr& upload_wait_semaphore : upload_wait_semaphores) {
frame_data_->frame->cmds()->AddWaitSemaphore(
std::move(upload_wait_semaphore), vk::PipelineStageFlagBits::eVertexInput |
vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eTransfer);
}
// Generate the Vulkan commands to render the frame.
render_queue_.Sort();
{
for (uint32_t camera_index = 0; camera_index < frame_data_->cameras.size(); ++camera_index) {
switch (config_.shadow_type) {
case PaperRendererShadowType::kNone:
GenerateCommandsForNoShadows(camera_index);
break;
case PaperRendererShadowType::kShadowVolume:
GenerateCommandsForShadowVolumes(camera_index);
break;
default:
FX_DCHECK(false) << "Unsupported shadow type: " << config_.shadow_type;
GenerateCommandsForNoShadows(camera_index);
}
}
}
render_queue_.Clear();
GenerateDebugCommands(frame_data_->frame->cmds());
frame_data_ = nullptr;
transform_stack_.Clear();
shape_cache_.EndFrame();
draw_call_factory_.EndFrame();
}
void PaperRenderer::DrawDebugText(std::string text, vk::Offset2D offset, int32_t scale) {
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
// TODO(fxbug.dev/7334): Add error checking to make sure math will not cause negative
// values or the bars to go off screen.
frame_data_->texts.push_back({text, offset, scale});
}
void PaperRenderer::DrawVLine(escher::DebugRects::Color kColor, uint32_t x_coord, int32_t y_start,
uint32_t y_end, uint32_t thickness) {
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
vk::Rect2D rect;
vk::Offset2D offset = {static_cast<int32_t>(x_coord), y_start};
vk::Extent2D extent = {static_cast<uint32_t>(x_coord + thickness), y_end};
// Adds error checking to make sure math will not cause negative
// values or the bars to go off screen.
FX_DCHECK(extent.width >= 0 && extent.width < frame_data_->output_image->width());
FX_DCHECK(extent.height >= 0 && extent.height < frame_data_->output_image->height());
rect.offset = offset;
rect.extent = extent;
frame_data_->lines.push_back({kColor, rect});
}
void PaperRenderer::DrawHLine(escher::DebugRects::Color kColor, int32_t y_coord, int32_t x_start,
uint32_t x_end, int32_t thickness) {
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
vk::Rect2D rect;
vk::Offset2D offset = {x_start, static_cast<int32_t>(y_coord)};
vk::Extent2D extent = {x_end, static_cast<uint32_t>(y_coord + thickness)};
// Adds error checking to make sure math will not cause negative
// values or the bars to go off screen.
FX_DCHECK(extent.width >= 0 && extent.width < frame_data_->output_image->width());
FX_DCHECK(extent.height >= 0 && extent.height < frame_data_->output_image->height());
rect.offset = offset;
rect.extent = extent;
frame_data_->lines.push_back({kColor, rect});
}
void PaperRenderer::BindSceneAndCameraUniforms(uint32_t camera_index) {
auto* cmd_buf = frame_data_->frame->cmds();
for (UniformBinding& binding : frame_data_->scene_uniform_bindings) {
binding.Bind(cmd_buf);
}
frame_data_->cameras[camera_index].binding.Bind(cmd_buf);
}
bool PaperRenderer::SupportsMaterial(const PaperMaterialPtr& material) {
if (!material) {
return false;
}
if (material->type() == Material::Type::kWireframe && !escher()->supports_wireframe()) {
FX_LOGS(ERROR) << "Device doesn't support feature fillModeNonSolid. "
"Draw Calls will not be enqueued.";
return false;
}
return true;
}
void PaperRenderer::Draw(PaperDrawable* drawable, PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::Draw");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
// For restoring state afterward.
size_t transform_stack_size = transform_stack_.size();
size_t num_clip_planes = transform_stack_.num_clip_planes();
drawable->DrawInScene(frame_data_->scene.get(), &draw_call_factory_, &transform_stack_,
frame_data_->frame.get(), flags);
transform_stack_.Clear({transform_stack_size, num_clip_planes});
}
void PaperRenderer::DrawCircle(float radius, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::DrawCircle");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
FX_DCHECK(material);
if (!SupportsMaterial(material)) {
return;
}
draw_call_factory_.DrawCircle(radius, *material.get(), flags);
}
void PaperRenderer::DrawRect(vec2 min, vec2 max, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::DrawRect");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
FX_DCHECK(material);
if (!SupportsMaterial(material)) {
return;
}
draw_call_factory_.DrawRect(min, max, *material.get(), flags);
}
// Convenience wrapper around the standard DrawRect function.
void PaperRenderer::DrawRect(float width, float height, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
const vec2 extent(width, height);
DrawRect(-0.5f * extent, 0.5f * extent, material, flags);
}
void PaperRenderer::DrawRoundedRect(const RoundedRectSpec& spec, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::DrawRoundedRect");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
FX_DCHECK(material);
if (!SupportsMaterial(material)) {
return;
}
draw_call_factory_.DrawRoundedRect(spec, *material.get(), flags);
}
void PaperRenderer::DrawBoundingBox(const BoundingBox& box, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::DrawBoundingBox");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
FX_DCHECK(material);
if (!SupportsMaterial(material)) {
return;
}
if (material->texture()) {
FX_LOGS(ERROR) << "TODO(fxbug.dev/7307): Box meshes do not currently support textures.";
return;
}
mat4 matrix = box.CreateTransform();
transform_stack_.PushTransform(matrix);
draw_call_factory_.DrawBoundingBox(*material.get(), flags);
transform_stack_.Pop();
}
void PaperRenderer::DrawMesh(const MeshPtr& mesh, const PaperMaterialPtr& material,
PaperDrawableFlags flags) {
TRACE_DURATION("gfx", "PaperRenderer::DrawMesh");
FX_DCHECK(frame_data_);
FX_DCHECK(!frame_data_->scene_finalized);
FX_DCHECK(material);
if (!SupportsMaterial(material)) {
return;
}
draw_call_factory_.DrawMesh(mesh, *material.get(), flags);
}
// TODO(fxbug.dev/7245): in "no shadows" mode, should we:
// - not use the other lights, and boost the ambient intensity?
// - still use the lights, allowing a BRDF, distance-based-falloff etc.
// The right answer is probably to separate the shadow algorithm from the
// lighting model.
void PaperRenderer::GenerateCommandsForNoShadows(uint32_t camera_index) {
TRACE_DURATION("gfx", "PaperRenderer::GenerateCommandsForNoShadows");
const FramePtr& frame = frame_data_->frame;
CommandBuffer* cmd_buf = frame->cmds();
RenderPassInfo render_pass_info;
FX_DCHECK(camera_index < frame_data_->cameras.size());
auto render_area = frame_data_->cameras[camera_index].rect;
if (!RenderPassInfo::InitRenderPassInfo(&render_pass_info, render_area, frame_data_->output_image,
frame_data_->depth_texture, frame_data_->msaa_texture,
escher()->image_view_allocator())) {
FX_LOGS(ERROR) << "PaperRenderer::GenerateCommandsForNoShadows(): "
"RenderPassInfo initialization failed. Exiting.";
return;
}
cmd_buf->BeginRenderPass(render_pass_info);
frame->AddTimestamp("started no-shadows render pass");
BindSceneAndCameraUniforms(camera_index);
const CameraData& cam_data = frame_data_->cameras[camera_index];
cmd_buf->SetViewport(cam_data.viewport);
cmd_buf->PushConstants(PaperShaderPushConstants{
.light_index = 0, // ignored
.eye_index = cam_data.eye_index,
});
{
PaperRenderQueueContext context;
context.set_shader_selector(PaperShaderListSelector::kAmbientLighting);
// Render wireframe.
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kWireframe);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kWireframe);
// Render opaque.
cmd_buf->SetWireframe(false);
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kOpaque);
// Render translucent.
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kTranslucent);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kTranslucent);
}
cmd_buf->EndRenderPass();
frame->AddTimestamp("finished no-shadows render pass");
}
void PaperRenderer::GenerateCommandsForShadowVolumes(uint32_t camera_index) {
TRACE_DURATION("gfx", "PaperRenderer::GenerateCommandsForShadowVolumes");
const uint32_t width = frame_data_->output_image->width();
const uint32_t height = frame_data_->output_image->height();
const FramePtr& frame = frame_data_->frame;
CommandBuffer* cmd_buf = frame->cmds();
RenderPassInfo render_pass_info;
FX_DCHECK(camera_index < frame_data_->cameras.size());
auto render_area = frame_data_->cameras[camera_index].rect;
if (!RenderPassInfo::InitRenderPassInfo(&render_pass_info, render_area, frame_data_->output_image,
frame_data_->depth_texture, frame_data_->msaa_texture,
escher()->image_view_allocator())) {
FX_LOGS(ERROR) << "PaperRenderer::GenerateCommandsForShadowVolumes(): "
"RenderPassInfo initialization failed. Exiting.";
return;
}
cmd_buf->BeginRenderPass(render_pass_info);
frame->AddTimestamp("started shadow_volume render pass");
BindSceneAndCameraUniforms(camera_index);
const CameraData& cam_data = frame_data_->cameras[camera_index];
cmd_buf->SetViewport(cam_data.viewport);
PaperRenderQueueContext context;
// Configure the render context for a depth/ambient "pass" (this isn't an
// actual Vulkan pass/subpass), and emit Vulkan commands into the command
// buffer.
{
cmd_buf->PushConstants(PaperShaderPushConstants{
.light_index = 0, // ignored
.eye_index = cam_data.eye_index,
});
context.set_shader_selector(PaperShaderListSelector::kAmbientLighting);
// Render wireframe.
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kWireframe);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kWireframe);
// Render opaque.
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kOpaque);
}
cmd_buf->SetStencilTest(true);
cmd_buf->SetDepthTestAndWrite(true, false);
cmd_buf->SetStencilFrontReference(0xff, 0xff, 0U);
cmd_buf->SetStencilBackReference(0xff, 0xff, 0U);
cmd_buf->SetBlendFactors(
/*src_color_blend=*/vk::BlendFactor::eOne, /*src_alpha_blend=*/vk::BlendFactor::eZero,
/*dst_color_blend=*/vk::BlendFactor::eOne, /*dst_alpha_blend=*/vk::BlendFactor::eOne);
cmd_buf->SetBlendOp(vk::BlendOp::eAdd);
// For each point light, emit Vulkan commands first to draw the stencil shadow
// geometry for that light, and then to add the lighting contribution for that
// light.
const uint32_t num_point_lights = static_cast<uint32_t>(frame_data_->scene->num_point_lights());
for (uint32_t i = 0; i < num_point_lights; ++i) {
// Some setup doesn't need to be done for the first light.
if (i != 0) {
// Must clear the stencil buffer for every light except the first one.
cmd_buf->ClearDepthStencilAttachmentRect(cam_data.rect.offset, cam_data.rect.extent,
render_pass_info.clear_depth_stencil,
vk::ImageAspectFlagBits::eStencil);
// Ensure that each light starts with blending disabled. Otherwise, the 2nd and subsequent
// lights would use a different pipeline for |shadow_volume_geometry_program_|.
cmd_buf->SetBlendEnable(false);
if (config_.debug) {
// Replace values set by the debug visualization.
cmd_buf->SetStencilTest(true);
cmd_buf->SetWireframe(false);
}
}
cmd_buf->PushConstants(PaperShaderPushConstants{
.light_index = i,
.eye_index = cam_data.eye_index,
});
// Emit commands for stencil shadow geometry.
{
context.set_shader_selector(PaperShaderListSelector::kShadowCaster);
// Draw front and back faces of the shadow volumes in a single pass. We
// use the standard approach of modifying the stencil buffer only when the
// depth test is passed, incrementing the stencil value for front-faces
// and decrementing it for back-faces.
cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone);
cmd_buf->SetStencilFrontOps(vk::CompareOp::eAlways, vk::StencilOp::eIncrementAndWrap,
vk::StencilOp::eKeep, vk::StencilOp::eKeep);
cmd_buf->SetStencilBackOps(vk::CompareOp::eAlways, vk::StencilOp::eDecrementAndWrap,
vk::StencilOp::eKeep, vk::StencilOp::eKeep);
// Leaving this as eLessOrEqual would result in total self-shadowing by
// all shadow-casters.
cmd_buf->SetDepthCompareOp(vk::CompareOp::eLess);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kShadowCaster);
}
// Emit commands for adding lighting contribution.
{
context.set_shader_selector(PaperShaderListSelector::kPointLighting);
cmd_buf->SetBlendEnable(true);
cmd_buf->SetCullMode(vk::CullModeFlagBits::eBack);
cmd_buf->SetDepthCompareOp(vk::CompareOp::eLessOrEqual);
cmd_buf->SetStencilFrontOps(vk::CompareOp::eEqual, vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep);
cmd_buf->SetStencilBackOps(vk::CompareOp::eAlways, vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kOpaque);
}
if (config_.debug) {
if (!escher_->supports_wireframe()) {
FX_LOGS(WARNING) << "Wireframe not supported; cannot visualize shadow volume geometry.";
} else {
context.set_shader_selector(PaperShaderListSelector::kShadowCasterDebug);
cmd_buf->SetBlendEnable(false);
cmd_buf->SetStencilTest(false);
cmd_buf->SetWireframe(true);
cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kShadowCaster);
}
}
}
// Draw translucent geometry without lighting.
context.set_shader_selector(PaperShaderListSelector::kAmbientLighting);
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kTranslucent);
render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kTranslucent);
cmd_buf->EndRenderPass();
frame->AddTimestamp("finished shadow_volume render pass");
}
void PaperRenderer::GenerateDebugCommands(CommandBuffer* cmd_buf) {
TRACE_DURATION("gfx", "PaperRenderer::GenerateDebugCommands");
// Exit early if there is no debug rendering to be done.
if (frame_data_->texts.size() == 0 && frame_data_->lines.size() == 0) {
return;
}
const FramePtr& frame = frame_data_->frame;
frame->AddTimestamp("started debug render pass");
auto& output_image = frame_data_->output_image;
auto swapchain_layout = output_image->swapchain_layout();
if (swapchain_layout == vk::ImageLayout::eUndefined) {
FX_LOGS(ERROR) << "PaperRenderer::GenerateDebugCommands(): "
"exiting due to undefined swapchain layout.";
return;
}
if (output_image->layout() != swapchain_layout) {
FX_LOGS(ERROR) << "PaperRenderer::GeneratedDebugCommands(): "
"Layout of output_image is not initialized to swapchain layout. Exiting.";
return;
}
cmd_buf->ImageBarrier(
output_image, swapchain_layout, vk::ImageLayout::eTransferDstOptimal,
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferWrite,
vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite);
{
TRACE_DURATION("gfx", "PaperRenderer::GenerateDebugCommands[text]");
for (std::size_t i = 0; i < frame_data_->texts.size(); i++) {
const TextData& td = frame_data_->texts[i];
debug_font_->Blit(cmd_buf, td.text, output_image, td.offset, td.scale);
}
}
{
TRACE_DURATION("gfx", "PaperRenderer::GenerateDebugCommands[lines]");
for (std::size_t i = 0; i < frame_data_->lines.size(); i++) {
const LineData& ld = frame_data_->lines[i];
debug_lines_->Blit(cmd_buf, ld.kColor, output_image, ld.rect);
}
}
cmd_buf->ImageBarrier(
output_image, vk::ImageLayout::eTransferDstOptimal, swapchain_layout,
vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite,
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferWrite);
frame->AddTimestamp("finished debug render pass");
}
// Helper for WarmPipelineAndRenderPassCaches(). Return the render-pass that should be used for
// pipeline creation for the specified config.
static impl::RenderPassPtr WarmRenderPassCache(impl::RenderPassCache* cache,
const PaperRendererConfig& config,
vk::Format output_format,
vk::ImageLayout output_swapchain_layout,
bool use_transient_attachments) {
TRACE_DURATION("gfx", "PaperRenderer::WarmRenderPassCache", "format",
vk::to_string(output_format), "layout", vk::to_string(output_swapchain_layout));
RenderPassInfo info;
RenderPassInfo::AttachmentInfo color_attachment_info;
color_attachment_info.format = output_format;
color_attachment_info.swapchain_layout = output_swapchain_layout;
color_attachment_info.sample_count = 1;
if (!RenderPassInfo::InitRenderPassInfo(&info, color_attachment_info, config.depth_stencil_format,
output_format, config.msaa_sample_count,
use_transient_attachments)) {
FX_LOGS(ERROR) << "WarmRenderPassCache(): InitRenderPassInfo failed. Exiting.";
return nullptr;
};
return cache->ObtainRenderPass(info, /*allow_render_pass_creation*/ true);
}
// Helper for WarmPipelineAndRenderPassCaches.
static void BindMeshSpecHelper(CommandBufferPipelineState* cbps, const MeshSpec& mesh_spec) {
const uint32_t total_attribute_count = mesh_spec.total_attribute_count();
BlockAllocator allocator(512);
RenderFuncs::VertexAttributeBinding* attribute_bindings =
RenderFuncs::NewVertexAttributeBindings(PaperRenderFuncs::kMeshAttributeBindingLocations,
&allocator, mesh_spec, total_attribute_count);
for (uint32_t i = 0; i < total_attribute_count; ++i) {
attribute_bindings[i].Bind(cbps);
}
// NOTE: we don't actually have a buffer to bind, nor an offset into the bound buffer. This would
// be a problem if we tried to generate a draw cmd, but is OK because we just need the stride and
// input-rate in order to pre-generate pipelines.
for (uint32_t i = 0; i < VulkanLimits::kNumVertexBuffers; ++i) {
cbps->BindVertices(i, vk::Buffer(), 0, mesh_spec.stride(i), vk::VertexInputRate::eVertex);
}
}
// Helper for WarmPipelineAndRenderPassCaches.
static void WarmProgramHelper(impl::PipelineLayoutCache* pipeline_layout_cache,
const ShaderProgramPtr& program, CommandBufferPipelineState* cbps,
const std::vector<SamplerPtr>& immutable_samplers) {
TRACE_DURATION("gfx", "PaperRenderer::WarmProgramHelper");
// Generate pipeline which doesn't require an immutable sampler.
PipelineLayoutPtr layout = program->ObtainPipelineLayout(pipeline_layout_cache, nullptr);
cbps->FlushGraphicsPipeline(layout.get(), program.get());
// Generate pipelines which require immutable samplers.
for (auto& sampler : immutable_samplers) {
PipelineLayoutPtr layout = program->ObtainPipelineLayout(pipeline_layout_cache, sampler);
cbps->FlushGraphicsPipeline(layout.get(), program.get());
}
}
// Populate caches with all render passes and pipelines required by |config|.
void PaperRenderer::WarmPipelineAndRenderPassCaches(
Escher* escher, const PaperRendererConfig& config, vk::Format output_format,
vk::ImageLayout output_swapchain_layout, const std::vector<SamplerPtr>& immutable_samplers,
bool use_protected_memory) {
TRACE_DURATION("gfx", "PaperRenderer::WarmPipelineAndRenderPassCaches");
// Determine whether transient attachments can be used for this render pass. Depending on the
// memory types provided by the Vulkan impl, the answer may differ when protected memory is/isn't
// being used.
const vk::MemoryPropertyFlags transient_memory_properties =
use_protected_memory
? vk::MemoryPropertyFlagBits::eLazilyAllocated |
vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eProtected
: vk::MemoryPropertyFlagBits::eLazilyAllocated | vk::MemoryPropertyFlagBits::eDeviceLocal;
const bool use_transient_attachments =
(0 != escher::impl::GetMemoryTypeIndices(escher->vk_physical_device(), 0xffffffff,
transient_memory_properties));
CommandBufferPipelineState cbps(escher->pipeline_builder()->GetWeakPtr());
// Obtain and set the render pass; this is the only render pass that is used, so we just need to
// set it once.
// TODO(fxbug.dev/44894): try to avoid using this "impl" type directly.
impl::RenderPassPtr render_pass =
WarmRenderPassCache(escher->render_pass_cache(), config, output_format,
output_swapchain_layout, use_transient_attachments);
FX_DCHECK(render_pass);
cbps.set_render_pass(render_pass.get());
// Set up vertex buffer bindings, as well as bindings to attributes within those buffers. Of
// course we don't actually have buffers right now; that's OK... see comments in the helper func
// for details.
{
TRACE_DURATION("gfx", "PaperRenderer::WarmPipelineAndRenderPassCaches[bind mesh spec]");
BindMeshSpecHelper(&cbps, PaperShapeCache::kShadowVolumeMeshSpec());
}
// NOTE: different mesh specs are used depending on whether stencil shadows
// are enabled. But it doesn't matter, because CommandBuffer will only use whichever attributes
// are required for the specified shader.
// TODO(fxbug.dev/44898): once kShadowVolumeMeshSpec and kStandardMeshSpec are constexpr, we
// should be able to use static_assert() here.
FX_DCHECK(PaperShapeCache::kShadowVolumeMeshSpec().attributes[0] ==
PaperShapeCache::kStandardMeshSpec().attributes[0]);
FX_DCHECK(PaperShapeCache::kShadowVolumeMeshSpec().attributes[1] ==
PaperShapeCache::kStandardMeshSpec().attributes[1]);
switch (config.shadow_type) {
case PaperRendererShadowType::kNone: {
if (escher->supports_wireframe()) {
cbps.SetToDefaultState(CommandBuffer::DefaultState::kWireframe);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kNoLightingProgramData), &cbps, immutable_samplers);
}
cbps.SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kAmbientLightProgramData), &cbps, immutable_samplers);
cbps.SetToDefaultState(CommandBuffer::DefaultState::kTranslucent);
WarmProgramHelper(escher->pipeline_layout_cache(), escher->GetProgram(kNoLightingProgramData),
&cbps, immutable_samplers);
} break;
case PaperRendererShadowType::kShadowVolume: {
// Wireframe shapes (not shadow volumes).
if (escher->supports_wireframe()) {
cbps.SetToDefaultState(CommandBuffer::DefaultState::kWireframe);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kNoLightingProgramData), &cbps, immutable_samplers);
}
// Ambient opaque.
{
cbps.SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kAmbientLightProgramData), &cbps, immutable_samplers);
}
// Set state common to both stencil shadow "geometry" and "lighting" passes.
cbps.SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
cbps.SetStencilTest(true);
cbps.SetDepthTestAndWrite(true, false);
cbps.SetBlendFactors(
/*src_color_blend=*/vk::BlendFactor::eOne, /*src_alpha_blend=*/vk::BlendFactor::eZero,
/*dst_color_blend=*/vk::BlendFactor::eOne, /*dst_alpha_blend=*/vk::BlendFactor::eOne);
cbps.SetBlendOp(vk::BlendOp::eAdd);
// Stencil shadow geometry.
{
cbps.SetCullMode(vk::CullModeFlagBits::eNone);
cbps.SetDepthCompareOp(vk::CompareOp::eLess);
cbps.SetStencilFrontOps(vk::CompareOp::eAlways, vk::StencilOp::eIncrementAndWrap,
vk::StencilOp::eKeep, vk::StencilOp::eKeep);
cbps.SetStencilBackOps(vk::CompareOp::eAlways, vk::StencilOp::eDecrementAndWrap,
vk::StencilOp::eKeep, vk::StencilOp::eKeep);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kShadowVolumeGeometryProgramData), &cbps,
immutable_samplers);
}
// Stencil shadow lighting.
{
cbps.SetBlendEnable(true);
cbps.SetCullMode(vk::CullModeFlagBits::eBack);
cbps.SetDepthCompareOp(vk::CompareOp::eLessOrEqual);
cbps.SetStencilFrontOps(vk::CompareOp::eEqual, vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep);
cbps.SetStencilBackOps(vk::CompareOp::eAlways, vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kPointLightProgramData), &cbps, immutable_samplers);
}
// Wireframe shadow volumes (for debug-mode).
if (escher->supports_wireframe()) {
cbps.SetBlendEnable(false);
cbps.SetStencilTest(false);
cbps.SetWireframe(true);
cbps.SetCullMode(vk::CullModeFlagBits::eNone);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kShadowVolumeGeometryDebugProgramData), &cbps,
immutable_samplers);
}
// Translucent.
{
cbps.SetToDefaultState(CommandBuffer::DefaultState::kTranslucent);
WarmProgramHelper(escher->pipeline_layout_cache(),
escher->GetProgram(kNoLightingProgramData), &cbps, immutable_samplers);
}
} break;
case PaperRendererShadowType::kEnumCount:
default:
FX_CHECK(false) << "unhandled shadow type";
}
}
vk::DeviceSize PaperRenderer::GetTransientImageMemoryCommitment() {
vk::DeviceSize num_bytes_committed = 0;
for (auto& texture : depth_buffers_) {
if (texture && texture->image()->is_transient()) {
num_bytes_committed += texture->image()->GetDeviceMemoryCommitment();
}
}
for (auto& texture : msaa_buffers_) {
if (texture && texture->image()->is_transient()) {
num_bytes_committed += texture->image()->GetDeviceMemoryCommitment();
}
}
return num_bytes_committed;
}
} // namespace escher