blob: ababafe4222758e8bf6c0bbb4c3bba906d7c466d [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 "lib/escher/paper/paper_renderer2.h"
#include <glm/gtc/matrix_access.hpp>
// TODO(ES-153): possibly delete. See comment below about NUM_CLIP_PLANES.
#include "lib/escher/geometry/clip_planes.h"
#include "lib/escher/escher.h"
#include "lib/escher/geometry/tessellation.h"
#include "lib/escher/impl/mesh_manager.h"
#include "lib/escher/paper/paper_legacy_drawable.h"
#include "lib/escher/paper/paper_render_queue_context.h"
#include "lib/escher/paper/paper_scene.h"
#include "lib/escher/paper/paper_shader_structs.h"
#include "lib/escher/renderer/batch_gpu_uploader.h"
#include "lib/escher/scene/object.h"
#include "lib/escher/util/string_utils.h"
#include "lib/escher/util/trace_macros.h"
#include "lib/escher/vk/command_buffer.h"
#include "lib/escher/vk/image.h"
#include "lib/escher/vk/render_pass_info.h"
#include "lib/escher/vk/shader_program.h"
#include "lib/escher/vk/texture.h"
namespace escher {
PaperRenderer2Ptr PaperRenderer2::New(EscherWeakPtr escher,
const PaperRendererConfig& config) {
return fxl::AdoptRef(new PaperRenderer2(std::move(escher), config));
}
PaperRenderer2::PaperRenderer2(EscherWeakPtr weak_escher,
const PaperRendererConfig& config)
: Renderer(weak_escher),
config_(config),
draw_call_factory_(weak_escher, config),
shape_cache_(std::move(weak_escher), config),
// TODO(ES-151): (probably) move programs into PaperDrawCallFactory.
ambient_light_program_(escher()->GetGraphicsProgram(
"shaders/model_renderer/main.vert",
"shaders/paper/frag/main_ambient_light.frag",
ShaderVariantArgs({
{"USE_ATTRIBUTE_UV", "1"},
// TODO(ES-153): currently required by main.vert.
{"NO_SHADOW_LIGHTING_PASS", "1"},
}))),
point_light_program_(escher()->GetGraphicsProgram(
"shaders/model_renderer/main.vert",
"shaders/paper/frag/main_point_light.frag",
ShaderVariantArgs({
{"USE_ATTRIBUTE_UV", "1"},
{"USE_PAPER_SHADER_POINT_LIGHT", "1"},
{"USE_PAPER_SHADER_PUSH_CONSTANTS", "1"},
{"SHADOW_VOLUME_POINT_LIGHTING", "1"},
}))),
point_light_falloff_program_(escher()->GetGraphicsProgram(
"shaders/model_renderer/main.vert",
"shaders/paper/frag/main_point_light.frag",
ShaderVariantArgs({
{"USE_ATTRIBUTE_UV", "1"},
{"USE_PAPER_SHADER_POINT_LIGHT", "1"},
{"USE_PAPER_SHADER_POINT_LIGHT_FALLOFF", "1"},
{"USE_PAPER_SHADER_PUSH_CONSTANTS", "1"},
{"SHADOW_VOLUME_POINT_LIGHTING", "1"},
}))),
shadow_volume_geometry_program_(escher()->GetGraphicsProgram(
"shaders/model_renderer/main.vert", "",
ShaderVariantArgs({
{"USE_ATTRIBUTE_BLEND_WEIGHT_1", "1"},
{"USE_PAPER_SHADER_POINT_LIGHT", "1"},
{"USE_PAPER_SHADER_PUSH_CONSTANTS", "1"},
{"SHADOW_VOLUME_EXTRUSION", "1"},
}))),
shadow_volume_geometry_debug_program_(escher()->GetGraphicsProgram(
"shaders/model_renderer/main.vert",
"shaders/model_renderer/main.frag",
ShaderVariantArgs({
{"USE_ATTRIBUTE_BLEND_WEIGHT_1", "1"},
{"USE_PAPER_SHADER_POINT_LIGHT", "1"},
{"USE_PAPER_SHADER_PUSH_CONSTANTS", "1"},
{"SHADOW_VOLUME_EXTRUSION", "1"},
}))) {
FXL_DCHECK(config.num_depth_buffers > 0);
depth_buffers_.resize(config.num_depth_buffers);
msaa_buffers_.resize(config.num_depth_buffers);
}
PaperRenderer2::~PaperRenderer2() { escher()->Cleanup(); }
PaperRenderer2::FrameData::FrameData(
const FramePtr& frame_in, const PaperScenePtr& scene_in,
const ImagePtr& output_image_in,
std::pair<TexturePtr, TexturePtr> depth_and_msaa_textures)
: frame(frame_in),
scene(scene_in),
output_image(output_image_in),
depth_texture(std::move(depth_and_msaa_textures.first)),
msaa_texture(std::move(depth_and_msaa_textures.second)),
gpu_uploader(BatchGpuUploader::New(frame->escher()->GetWeakPtr())) {}
PaperRenderer2::FrameData::~FrameData() = default;
void PaperRenderer2::SetConfig(const PaperRendererConfig& config) {
FXL_DCHECK(!frame_data_) << "Illegal call to SetConfig() during a frame.";
FXL_DCHECK(SupportsShadowType(config.shadow_type))
<< "Unsupported shadow type: " << config.shadow_type;
FXL_DCHECK(config.num_depth_buffers > 0);
FXL_DCHECK(config.msaa_sample_count == 1 || config.msaa_sample_count == 2 ||
config.msaa_sample_count == 4);
if (config.msaa_sample_count != config_.msaa_sample_count) {
FXL_VLOG(1) << "PaperRenderer2: MSAA sample count set to: "
<< config.msaa_sample_count
<< " (was: " << config_.msaa_sample_count << ")";
depth_buffers_.clear();
msaa_buffers_.clear();
}
if (config.num_depth_buffers != config_.num_depth_buffers) {
FXL_VLOG(1) << "PaperRenderer2: 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 PaperRenderer2::SupportsShadowType(
PaperRendererShadowType shadow_type) const {
return shadow_type == PaperRendererShadowType::kNone ||
shadow_type == PaperRendererShadowType::kShadowVolume;
}
void PaperRenderer2::BeginFrame(const FramePtr& frame,
const PaperScenePtr& scene,
const Camera& camera,
const ImagePtr& output_image) {
TRACE_DURATION("gfx", "PaperRenderer2::BeginFrame");
FXL_DCHECK(!frame_data_);
frame_data_ = std::make_unique<FrameData>(
frame, scene, output_image,
ObtainDepthAndMsaaTextures(frame, output_image->info()));
frame->command_buffer()->TakeWaitSemaphore(
output_image, vk::PipelineStageFlagBits::eColorAttachmentOutput);
shape_cache_.BeginFrame(frame_data_->gpu_uploader.get(),
frame->frame_number());
frame_data_->scene_uniform_bindings =
draw_call_factory_.BeginFrame(frame, scene.get(), &transform_stack_,
&render_queue_, &shape_cache_, camera);
}
void PaperRenderer2::EndFrame() {
TRACE_DURATION("gfx", "PaperRenderer2::EndFrame");
FXL_DCHECK(frame_data_);
frame_data_->gpu_uploader->Submit();
render_queue_.Sort();
{
auto* cmd_buf = frame_data_->frame->cmds();
for (UniformBinding& binding : frame_data_->scene_uniform_bindings) {
binding.Bind(cmd_buf);
}
switch (config_.shadow_type) {
case PaperRendererShadowType::kNone:
GenerateCommandsForNoShadows();
break;
case PaperRendererShadowType::kShadowVolume:
GenerateCommandsForShadowVolumes();
break;
default:
FXL_DCHECK(false) << "Unsupported shadow type: " << config_.shadow_type;
GenerateCommandsForNoShadows();
}
}
render_queue_.Clear();
frame_data_ = nullptr;
transform_stack_.Clear();
shape_cache_.EndFrame();
draw_call_factory_.EndFrame();
}
void PaperRenderer2::Draw(PaperDrawable* drawable, PaperDrawableFlags flags,
mat4* matrix) {
TRACE_DURATION("gfx", "PaperRenderer2::Draw");
// For restoring state afterward.
size_t transform_stack_size = transform_stack_.size();
size_t num_clip_planes = transform_stack_.num_clip_planes();
if (matrix) {
transform_stack_.PushTransform(*matrix);
}
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 PaperRenderer2::DrawCircle(float radius, const PaperMaterialPtr& material,
PaperDrawableFlags flags, mat4* matrix) {
TRACE_DURATION("gfx", "PaperRenderer2::DrawCircle");
if (!material)
return;
if (!matrix) {
draw_call_factory_.DrawCircle(radius, *material.get(), flags);
} else {
// Roll the radius into the transform to avoid an extra push onto the stack;
// see PaperDrawCallFactory::DrawCircle() for details.
transform_stack_.PushTransform(
glm::scale(*matrix, vec3(radius, radius, radius)));
draw_call_factory_.DrawCircle(1.f, *material.get(), flags);
transform_stack_.Pop();
}
}
void PaperRenderer2::DrawRect(vec2 min, vec2 max,
const PaperMaterialPtr& material,
PaperDrawableFlags flags, mat4* matrix) {
TRACE_DURATION("gfx", "PaperRenderer2::DrawRect");
if (!material)
return;
if (!matrix) {
draw_call_factory_.DrawRect(min, max, *material.get(), flags);
} else {
transform_stack_.PushTransform(*matrix);
draw_call_factory_.DrawRect(min, max, *material.get(), flags);
transform_stack_.Pop();
}
}
void PaperRenderer2::DrawRoundedRect(const RoundedRectSpec& spec,
const PaperMaterialPtr& material,
PaperDrawableFlags flags, mat4* matrix) {
TRACE_DURATION("gfx", "PaperRenderer2::DrawRoundedRect");
if (!material)
return;
if (!matrix) {
draw_call_factory_.DrawRoundedRect(spec, *material.get(), flags);
} else {
transform_stack_.PushTransform(*matrix);
draw_call_factory_.DrawRoundedRect(spec, *material.get(), flags);
transform_stack_.Pop();
}
}
void PaperRenderer2::DrawLegacyObject(const Object& obj,
PaperDrawableFlags flags) {
FXL_DCHECK(frame_data_);
PaperLegacyDrawable drawable(obj);
Draw(&drawable, flags);
}
void PaperRenderer2::InitRenderPassInfo(RenderPassInfo* rp) {
const ImagePtr& output_image = frame_data_->output_image;
const TexturePtr& depth_texture = frame_data_->depth_texture;
const TexturePtr& msaa_texture = frame_data_->msaa_texture;
static constexpr uint32_t kRenderTargetAttachmentIndex = 0;
static constexpr uint32_t kResolveTargetAttachmentIndex = 1;
{
rp->color_attachments[kRenderTargetAttachmentIndex] =
ImageView::New(escher()->resource_recycler(), output_image);
rp->num_color_attachments = 1;
// Clear and store color attachment 0, the sole color attachment.
rp->clear_attachments = 1u << kRenderTargetAttachmentIndex;
rp->store_attachments = 1u << kRenderTargetAttachmentIndex;
// NOTE: we don't need to keep |depth_texture| alive explicitly because it
// will be kept alive by the render-pass.
rp->depth_stencil_attachment = depth_texture;
// 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;
rp->clear_color[0].setFloat32({0.1f, 0.1f, 0.2f, 1.f});
// If MSAA is enabled, we need to explicitly specify the sub-pass in order
// to specify the resolve attachment. Otherwise we allow a default subclass
// to be created.
if (msaa_texture) {
FXL_DCHECK(rp->num_color_attachments == 1 && rp->clear_attachments == 1u);
// Move the output image to attachment #1, so that attachment #0 is always
// the attachment that we render into.
rp->color_attachments[kResolveTargetAttachmentIndex] =
std::move(rp->color_attachments[kRenderTargetAttachmentIndex]);
rp->color_attachments[kRenderTargetAttachmentIndex] = msaa_texture;
rp->num_color_attachments = 2;
// Now that the output image is attachment #1, that's the one we need to
// store.
rp->store_attachments = 1u << kResolveTargetAttachmentIndex;
rp->subpasses.push_back(RenderPassInfo::Subpass{
.num_color_attachments = 1,
.color_attachments = {kRenderTargetAttachmentIndex},
.num_input_attachments = 0,
.input_attachments = {},
.num_resolve_attachments = 1,
.resolve_attachments = {kResolveTargetAttachmentIndex},
});
}
}
FXL_DCHECK(rp->Validate());
}
// TODO(ES-154): 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 PaperRenderer2::GenerateCommandsForNoShadows() {
TRACE_DURATION("gfx", "PaperRenderer2::GenerateCommandsForNoShadows");
const FramePtr& frame = frame_data_->frame;
CommandBuffer* cmd_buf = frame->cmds();
RenderPassInfo render_pass_info;
InitRenderPassInfo(&render_pass_info);
cmd_buf->BeginRenderPass(render_pass_info);
frame->AddTimestamp("started no-shadows render pass");
{
PaperRenderQueueContext context;
context.set_draw_mode(PaperRendererDrawMode::kAmbient);
context.set_shader_program(ambient_light_program_);
cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque);
render_queue_.GenerateCommands(cmd_buf, &context,
PaperRenderQueueFlagBits::kOpaque);
}
cmd_buf->EndRenderPass();
frame->AddTimestamp("finished no-shadows render pass");
}
void PaperRenderer2::GenerateCommandsForShadowVolumes() {
TRACE_DURATION("gfx", "PaperRenderer2::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;
InitRenderPassInfo(&render_pass_info);
cmd_buf->BeginRenderPass(render_pass_info);
frame->AddTimestamp("started shadow_volume render pass");
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.
{
context.set_draw_mode(PaperRendererDrawMode::kAmbient);
context.set_shader_program(ambient_light_program_);
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);
// 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 = frame_data_->scene->num_point_lights();
for (uint32_t i = 0; i < num_point_lights; ++i) {
// Must clear the stencil buffer for every light except the first one.
if (i != 0) {
cmd_buf->ClearDepthStencilAttachmentRect(
{0, 0}, {width, height}, render_pass_info.clear_depth_stencil,
vk::ImageAspectFlagBits::eStencil);
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});
// Emit commands for stencil shadow geometry.
{
context.set_draw_mode(PaperRendererDrawMode::kShadowVolumeGeometry);
context.set_shader_program(shadow_volume_geometry_program_);
// 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::kOpaque);
}
// Emit commands for adding lighting contribution.
{
context.set_draw_mode(PaperRendererDrawMode::kShadowVolumeLighting);
// Use a slightly less expensive shader when distance-based attenuation is
// disabled.
const bool use_light_falloff =
frame_data_->scene->point_lights[i].falloff > 0.f;
if (use_light_falloff) {
context.set_shader_program(point_light_falloff_program_);
} else {
context.set_shader_program(point_light_program_);
}
cmd_buf->SetBlendEnable(true);
cmd_buf->SetBlendFactors(vk::BlendFactor::eOne, vk::BlendFactor::eZero,
vk::BlendFactor::eOne, vk::BlendFactor::eOne);
cmd_buf->SetBlendOp(vk::BlendOp::eAdd);
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) {
context.set_draw_mode(PaperRendererDrawMode::kShadowVolumeGeometry);
context.set_shader_program(shadow_volume_geometry_debug_program_);
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::kOpaque);
}
}
cmd_buf->EndRenderPass();
frame->AddTimestamp("finished shadow_volume render pass");
}
std::pair<TexturePtr, TexturePtr> PaperRenderer2::ObtainDepthAndMsaaTextures(
const FramePtr& frame, const ImageInfo& info) {
FXL_DCHECK(!depth_buffers_.empty());
// Support for other sample_counts should fairly easy to add, if necessary.
FXL_DCHECK(info.sample_count == 1);
auto index = frame->frame_number() % depth_buffers_.size();
TexturePtr& depth_texture = depth_buffers_[index];
TexturePtr& msaa_texture = msaa_buffers_[index];
if (!depth_texture || info.width != depth_texture->width() ||
info.height != depth_texture->height() ||
config_.msaa_sample_count !=
depth_texture->image()->info().sample_count) {
// Need to generate a new depth buffer.
{
TRACE_DURATION("gfx",
"PaperRenderer2::ObtainDepthAndMsaaTextures (new depth)");
depth_texture = escher()->NewAttachmentTexture(
vk::Format::eD24UnormS8Uint, info.width, info.height,
config_.msaa_sample_count, vk::Filter::eLinear);
}
// If the sample count is 1, there is no need for a MSAA buffer.
if (config_.msaa_sample_count == 1) {
msaa_texture = nullptr;
} else {
TRACE_DURATION("gfx",
"PaperRenderer2::ObtainDepthAndMsaaTextures (new msaa)");
// TODO(SCN-634): use lazy memory allocation and transient attachments
// when available.
msaa_texture = escher()->NewAttachmentTexture(
info.format, info.width, info.height, config_.msaa_sample_count,
vk::Filter::eLinear
// TODO(ES-73): , vk::ImageUsageFlagBits::eTransientAttachment);
);
frame->cmds()->ImageBarrier(
msaa_texture->image(), vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal,
vk::PipelineStageFlagBits::eAllGraphics, vk::AccessFlags(),
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentWrite);
}
}
return {depth_texture, msaa_texture};
}
} // namespace escher