blob: e8c211fd5adc32eb17102e69bc9ab4d8c89fac98 [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_draw_call_factory.h"
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/paper/paper_material.h"
#include "src/ui/lib/escher/paper/paper_render_funcs.h"
#include "src/ui/lib/escher/paper/paper_render_queue.h"
#include "src/ui/lib/escher/paper/paper_render_queue_flags.h"
#include "src/ui/lib/escher/paper/paper_renderer_config.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/paper/paper_shape_cache.h"
#include "src/ui/lib/escher/paper/paper_transform_stack.h"
#include "src/ui/lib/escher/renderer/frame.h"
#include "src/ui/lib/escher/renderer/render_queue_item.h"
#include "src/ui/lib/escher/shape/mesh.h"
#include "src/ui/lib/escher/util/hasher.h"
#include "src/ui/lib/escher/util/trace_macros.h"
#include "src/ui/lib/escher/vk/shader_program.h"
#include <glm/gtc/matrix_access.hpp>
namespace escher {
namespace {
// Default 1x1 texture for Materials that have no texture. See header file
// |white_texture_| comment.
TexturePtr CreateWhiteTexture(Escher* escher, BatchGpuUploader* gpu_uploader) {
FX_DCHECK(escher);
uint8_t channels[4];
channels[0] = channels[1] = channels[2] = channels[3] = 255;
auto image = escher->NewRgbaImage(gpu_uploader, 1, 1, channels);
return escher->NewTexture(std::move(image), vk::Filter::eNearest);
}
PaperDrawCallFactory::SortKey GetSortKey(const Material& mat, Hash pipeline_hash, Hash draw_hash,
float depth) {
auto type = mat.type();
switch (type) {
case Material::Type::kTranslucent:
return PaperDrawCallFactory::SortKey::NewTranslucent(pipeline_hash, draw_hash, depth);
case Material::Type::kWireframe:
return PaperDrawCallFactory::SortKey::NewWireframe(pipeline_hash, draw_hash, depth);
case Material::Type::kOpaque:
default:
return PaperDrawCallFactory::SortKey::NewOpaque(pipeline_hash, draw_hash, depth);
}
}
PaperRenderQueueFlagBits GetRenderQueueFlagBits(const Material& mat) {
auto type = mat.type();
switch (type) {
case Material::Type::kTranslucent:
return PaperRenderQueueFlagBits::kTranslucent;
case Material::Type::kWireframe:
return PaperRenderQueueFlagBits::kWireframe;
case Material::Type::kOpaque:
default:
return PaperRenderQueueFlagBits::kOpaque;
}
}
} // anonymous namespace
PaperDrawCallFactory::PaperDrawCallFactory(EscherWeakPtr weak_escher,
const PaperRendererConfig& config)
: config_(config),
ambient_light_program_(weak_escher->GetProgram(kAmbientLightProgramData)),
no_lighting_program_(weak_escher->GetProgram(kNoLightingProgramData)),
point_light_program_(weak_escher->GetProgram(kPointLightProgramData)),
shadow_volume_geometry_program_(weak_escher->GetProgram(kShadowVolumeGeometryProgramData)),
shadow_volume_geometry_debug_program_(
weak_escher->GetProgram(kShadowVolumeGeometryDebugProgramData)) {}
PaperDrawCallFactory::~PaperDrawCallFactory() { FX_DCHECK(!frame_); }
void PaperDrawCallFactory::DrawCircle(float radius, const PaperMaterial& material,
PaperDrawableFlags flags) {
FX_DCHECK(frame_);
// We aim to improve cache hit rate by using a circle of radius 1. This
// requires us to push a new transform.
const bool scale_radius = (radius != 1.f);
const auto& transform =
scale_radius ? transform_stack_->PushScale(radius) : transform_stack_->Top();
const auto& entry =
shape_cache_->GetCircleMesh(1.f, transform.clip_planes.data(), transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
if (scale_radius)
transform_stack_->Pop();
}
void PaperDrawCallFactory::DrawRect(vec2 min, vec2 max, const PaperMaterial& material,
PaperDrawableFlags flags) {
FX_DCHECK(frame_);
const auto& transform = transform_stack_->Top();
const auto& entry = shape_cache_->GetRectMesh(min, max, transform.clip_planes.data(),
transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::DrawRoundedRect(const RoundedRectSpec& spec,
const PaperMaterial& material,
PaperDrawableFlags flags) {
FX_DCHECK(frame_);
const auto& transform = transform_stack_->Top();
const auto& entry = shape_cache_->GetRoundedRectMesh(spec, transform.clip_planes.data(),
transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::DrawBoundingBox(const PaperMaterial& material,
PaperDrawableFlags flags) {
FX_DCHECK(frame_);
const auto& transform = transform_stack_->Top();
const auto& entry =
shape_cache_->GetBoxMesh(transform.clip_planes.data(), transform.clip_planes.size());
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::DrawMesh(const MeshPtr& mesh, const PaperMaterial& material,
PaperDrawableFlags flags) {
FX_DCHECK(frame_);
PaperShapeCacheEntry entry = {shape_cache_->frame_number(), mesh, mesh->num_indices(), 0};
EnqueueDrawCalls(entry, material, flags);
}
void PaperDrawCallFactory::EnqueueDrawCalls(const PaperShapeCacheEntry& cache_entry,
const PaperMaterial& material,
PaperDrawableFlags drawable_flags) {
FX_DCHECK(frame_);
if (!cache_entry) {
return;
}
TRACE_DURATION("gfx", "PaperDrawCallFactory::EnqueueDrawCalls");
if (track_cache_entries_) {
tracked_cache_entries_.push_back(cache_entry);
return; // No need to do anything else.
}
auto* mesh = cache_entry.mesh.get();
const auto& texture = material.texture() ? material.texture() : white_texture_;
const auto& transform = transform_stack_->Top();
Hash pipeline_hash;
Hash mesh_hash;
// Only the program goes into the pipeline hash. If we also wanted e.g. some
// objects to be stencil-tested and others not, this info would be included.
{
Hasher h;
// TODO(fxbug.dev/7241): add this back in some way, with a more abstract pipeline
// identifier instead of the actual program uid (which can change from pass
// to pass). h.u64(shadow_volume_program_->uid());
pipeline_hash = h.value();
}
// The object-hash is used to look up an existing MeshData for this
// Mesh/Material pair, and is also used as part of the sort-key below.
// We don't need to take opacity into account because separate RenderQueues
// are used for opaque vs. translucent objects.
{
Hasher h;
h.u64(mesh->uid());
h.u64(texture->uid());
mesh_hash = h.value();
}
PaperRenderFuncs::MeshData* mesh_data;
auto it = object_data_.find(mesh_hash);
if (it != object_data_.end()) {
mesh_data = reinterpret_cast<PaperRenderFuncs::MeshData*>(it->second);
} else {
mesh_data = PaperRenderFuncs::NewMeshData(frame_, mesh, texture);
object_data_.insert(it, std::make_pair(mesh_hash, mesh_data));
}
// Compute a depth metric for sorting objects.
#if 1
// As long as the camera is above the top of the viewing volume and the scene
// is composed of parallel-planar surfaces, we can simply subtract the
// object's elevation from the camera's elevation. Given these constraints,
// this metric is superior to the alternate one below, which can provide
// incorrect results at glancing angles (i.e. where the center of one object
// is closer to the camera than the other, but is nevertheless partly behind
// the other object from the camera's perspective.
const float depth = -(camera_pos_.z - transform.matrix[3][2]);
#else
// Compute the vector from the camera to the object, and project it against
// the camera's direction to obtain the depth.
const float depth = glm::dot(vec3(transform[3]) - camera_pos_, camera_dir_);
#endif
// Allocate and initialize per-instance data.
const vec4 material_color = material.GetPremultipliedRgba();
const bool cast_shadows = config_.shadow_type == PaperRendererShadowType::kShadowVolume &&
!(drawable_flags & PaperDrawableFlagBits::kDisableShadowCasting);
const PaperShaderList shader_list = GetShaderList(material, cast_shadows);
const bool needs_gamma_correction =
texture->is_yuv_format() || (drawable_flags & PaperDrawableFlagBits::kBt709Oetf);
auto draw_data = PaperRenderFuncs::NewMeshDrawData(
frame_, transform.matrix, material_color, /*gamma_power*/ needs_gamma_correction ? 2.f : 1.f,
shader_list, cache_entry.num_indices);
auto sort_key = GetSortKey(material, pipeline_hash, mesh_hash, depth).key();
auto queue_flags = GetRenderQueueFlagBits(material);
render_queue_->PushDrawCall(
{.render_queue_item = {.sort_key = sort_key,
.object_data = mesh_data,
.instance_data = draw_data,
.render_queue_func = PaperRenderFuncs::RenderMesh},
.render_queue_flags = queue_flags});
// Generate additional draw calls for stencil shadow volumes.
if (cast_shadows) {
// Generate an additional draw call.
draw_data = PaperRenderFuncs::NewMeshDrawData(frame_, transform.matrix, material_color,
/*gamma_power*/ 1.f, shader_list,
cache_entry.num_shadow_volume_indices);
// TODO(fxbug.dev/7241): revisit sort key... we expect that a subsequent CL will add a
// ShaderProgram as a field in the |instance_data| created by NewMeshDrawData(). Then, we'll
// want the sort key to reflect the fact that a different pipeline is being used.
render_queue_->PushDrawCall(
{.render_queue_item = {.sort_key = sort_key,
.object_data = mesh_data,
.instance_data = draw_data,
.render_queue_func = PaperRenderFuncs::RenderMesh},
.render_queue_flags = PaperRenderQueueFlagBits::kShadowCaster});
}
}
PaperShaderList PaperDrawCallFactory::GetShaderList(const Material& mat, bool cast_shadows) const {
PaperShaderList list;
switch (mat.type()) {
case Material::Type::kTranslucent: {
list.set_shader(PaperShaderListSelector::kAmbientLighting, no_lighting_program_.get());
break;
}
case Material::Type::kWireframe: {
list.set_shader(PaperShaderListSelector::kAmbientLighting, no_lighting_program_.get());
break;
}
case Material::Type::kOpaque: {
list.set_shader(PaperShaderListSelector::kAmbientLighting, ambient_light_program_.get());
list.set_shader(PaperShaderListSelector::kPointLighting, point_light_program_.get());
if (cast_shadows) {
FX_DCHECK(config_.shadow_type == PaperRendererShadowType::kShadowVolume);
list.set_shader(PaperShaderListSelector::kShadowCaster,
shadow_volume_geometry_program_.get());
list.set_shader(PaperShaderListSelector::kShadowCasterDebug,
shadow_volume_geometry_debug_program_.get());
}
break;
}
}
return list;
}
void PaperDrawCallFactory::SetConfig(const PaperRendererConfig& config) {
FX_DCHECK(!frame_) << "Illegal call to SetConfig() during a frame.";
FX_DCHECK(config.shadow_type == PaperRendererShadowType::kNone ||
config.shadow_type == PaperRendererShadowType::kShadowVolume);
config_ = config;
}
void PaperDrawCallFactory::BeginFrame(const FramePtr& frame, BatchGpuUploader* gpu_uploader,
PaperScene* scene, PaperTransformStack* transform_stack,
PaperRenderQueue* render_queue, PaperShapeCache* shape_cache,
vec3 camera_pos, vec3 camera_dir) {
FX_DCHECK(!frame_ && frame && gpu_uploader && transform_stack && render_queue && shape_cache);
frame_ = frame;
transform_stack_ = transform_stack;
render_queue_ = render_queue;
shape_cache_ = shape_cache;
camera_pos_ = camera_pos;
camera_dir_ = camera_dir;
tracked_cache_entries_.clear();
if (!white_texture_) {
white_texture_ = CreateWhiteTexture(frame->escher(), gpu_uploader);
}
}
void PaperDrawCallFactory::EndFrame() {
FX_DCHECK(frame_);
frame_ = nullptr;
transform_stack_ = nullptr;
render_queue_ = nullptr;
shape_cache_ = nullptr;
object_data_.clear();
camera_pos_ = vec3(0, 0, 0);
camera_dir_ = vec3(0, 0, 0);
}
PaperDrawCallFactory::SortKey PaperDrawCallFactory::SortKey::NewOpaque(Hash pipeline_hash,
Hash draw_hash,
float depth) {
// Depth must be non-negative, otherwise comparing the bit representations
// won't work.
if (depth < 0.f) {
depth = 0.f;
}
// Prioritize minimizing pipeline changes over depth-sorting; both are more
// important than minimizing mesh/texture state changes (in practice, almost
// every draw call uses a separate mesh/texture anyway).
// TODO(fxbug.dev/7241): We currently don't have multiple pipelines used in the opaque
// pass, so we sort primarily by depth. However, when we eventually do have
// multiple pipelines, we may want to rewrite the pipeline hashes with a value
// that reflects whether objects drawn using that pipeline tend to be drawn
// in front or back. For example, if we wanted to draw the background with a
// shiny metallic BRDF, and everything else with a matte diffuse shader then
// we should definitely draw the background last to avoid drawing expensive
// pixels that will later be overdrawn; this isn't guaranteed if we sort by
// the pipeline hash.
uint64_t depth_key(glm::floatBitsToUint(depth));
return SortKey((pipeline_hash.val << 48) | depth_key << 16 | (draw_hash.val & 0xffff));
}
PaperDrawCallFactory::SortKey PaperDrawCallFactory::SortKey::NewTranslucent(Hash pipeline_hash,
Hash draw_hash,
float depth) {
// Depth must be non-negative, otherwise comparing the bit representations
// won't work.
if (depth < 0.f) {
depth = 0.f;
}
// Prioritize back-to-front order over state changes.
uint64_t depth_key(glm::floatBitsToUint(depth) ^ 0xffffffffu);
return SortKey((depth_key << 32) | (pipeline_hash.val & 0xffff0000u) | (draw_hash.val & 0xffffu));
}
PaperDrawCallFactory::SortKey PaperDrawCallFactory::SortKey::NewWireframe(Hash pipeline_hash,
Hash draw_hash,
float depth) {
// Simply use opaque function for now, we may want to do this differently in the future.
return NewOpaque(pipeline_hash, draw_hash, depth);
}
} // namespace escher