| // 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 "src/ui/lib/escher/debug/debug_font.h" |
| #include "src/ui/lib/escher/debug/debug_rects.h" |
| #include "src/ui/lib/escher/escher.h" |
| // TODO(https://fxbug.dev/42121358): try to avoid including an "impl" file. |
| #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" |
| #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" |
| |
| #include <glm/gtc/matrix_access.hpp> |
| #include <vulkan/vulkan.hpp> |
| |
| // In full 3d use-cases, we may not want to display backfacing triangles. For now, |
| // we disable backface culling everywhere so that we can flip rendered content. |
| static constexpr bool kDisableBackfaceCulling = true; |
| |
| 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_LOGS(DEBUG) << "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_LOGS(DEBUG) << "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_LOGS(DEBUG) << "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(https://fxbug.dev/42152668): 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(https://fxbug.dev/42152901): 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(https://fxbug.dev/42152601): 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(https://fxbug.dev/42151912): 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); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kWireframe); |
| |
| // Render opaque. |
| cmd_buf->SetWireframe(false); |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kOpaque); |
| |
| // Render translucent. |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kTranslucent); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| render_queue_.GenerateCommands(cmd_buf, &context, PaperRenderQueueFlagBits::kWireframe); |
| |
| // Render opaque. |
| cmd_buf->SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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(kDisableBackfaceCulling ? vk::CullModeFlagBits::eNone |
| : 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); |
| if (kDisableBackfaceCulling) { |
| cmd_buf->SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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(https://fxbug.dev/42121358): 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(https://fxbug.dev/42121362): 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); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| WarmProgramHelper(escher->pipeline_layout_cache(), |
| escher->GetProgram(kNoLightingProgramData), &cbps, immutable_samplers); |
| } |
| |
| cbps.SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| WarmProgramHelper(escher->pipeline_layout_cache(), |
| escher->GetProgram(kAmbientLightProgramData), &cbps, immutable_samplers); |
| |
| cbps.SetToDefaultState(CommandBuffer::DefaultState::kTranslucent); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| WarmProgramHelper(escher->pipeline_layout_cache(), |
| escher->GetProgram(kNoLightingProgramData), &cbps, immutable_samplers); |
| } |
| |
| // Ambient opaque. |
| { |
| cbps.SetToDefaultState(CommandBuffer::DefaultState::kOpaque); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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(kDisableBackfaceCulling ? vk::CullModeFlagBits::eNone |
| : 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); |
| if (kDisableBackfaceCulling) { |
| cbps.SetCullMode(vk::CullModeFlagBits::eNone); |
| } |
| 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 |