323 lines
11 KiB
C++
323 lines
11 KiB
C++
/* Copyright (c) 2019-2025, Arm Limited and Contributors
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 the "License";
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "rendering/subpasses/geometry_subpass.h"
|
|
#include "common/utils.h"
|
|
#include "common/vk_common.h"
|
|
#include "rendering/render_context.h"
|
|
#include "scene_graph/components/camera.h"
|
|
#include "scene_graph/components/image.h"
|
|
#include "scene_graph/components/material.h"
|
|
#include "scene_graph/components/mesh.h"
|
|
#include "scene_graph/components/pbr_material.h"
|
|
#include "scene_graph/components/texture.h"
|
|
#include "scene_graph/node.h"
|
|
#include "scene_graph/scene.h"
|
|
|
|
namespace vkb
|
|
{
|
|
GeometrySubpass::GeometrySubpass(RenderContext &render_context, ShaderSource &&vertex_source, ShaderSource &&fragment_source, sg::Scene &scene_, sg::Camera &camera) :
|
|
Subpass{render_context, std::move(vertex_source), std::move(fragment_source)},
|
|
meshes{scene_.get_components<sg::Mesh>()},
|
|
camera{camera},
|
|
scene{scene_}
|
|
{
|
|
}
|
|
|
|
void GeometrySubpass::prepare()
|
|
{
|
|
// Build all shader variance upfront
|
|
auto &device = get_render_context().get_device();
|
|
for (auto &mesh : meshes)
|
|
{
|
|
for (auto &sub_mesh : mesh->get_submeshes())
|
|
{
|
|
auto &variant = sub_mesh->get_shader_variant();
|
|
auto &vert_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), variant);
|
|
auto &frag_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), variant);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeometrySubpass::get_sorted_nodes(std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &opaque_nodes, std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &transparent_nodes)
|
|
{
|
|
auto camera_transform = camera.get_node()->get_transform().get_world_matrix();
|
|
|
|
for (auto &mesh : meshes)
|
|
{
|
|
for (auto &node : mesh->get_nodes())
|
|
{
|
|
auto node_transform = node->get_transform().get_world_matrix();
|
|
|
|
const sg::AABB &mesh_bounds = mesh->get_bounds();
|
|
|
|
sg::AABB world_bounds{mesh_bounds.get_min(), mesh_bounds.get_max()};
|
|
world_bounds.transform(node_transform);
|
|
|
|
float distance = glm::length(glm::vec3(camera_transform[3]) - world_bounds.get_center());
|
|
|
|
for (auto &sub_mesh : mesh->get_submeshes())
|
|
{
|
|
if (sub_mesh->get_material()->alpha_mode == sg::AlphaMode::Blend)
|
|
{
|
|
transparent_nodes.emplace(distance, std::make_pair(node, sub_mesh));
|
|
}
|
|
else
|
|
{
|
|
opaque_nodes.emplace(distance, std::make_pair(node, sub_mesh));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeometrySubpass::draw(vkb::core::CommandBufferC &command_buffer)
|
|
{
|
|
std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> opaque_nodes;
|
|
std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> transparent_nodes;
|
|
|
|
get_sorted_nodes(opaque_nodes, transparent_nodes);
|
|
|
|
// Draw opaque objects in front-to-back order
|
|
{
|
|
ScopedDebugLabel opaque_debug_label{command_buffer, "Opaque objects"};
|
|
|
|
for (auto node_it = opaque_nodes.begin(); node_it != opaque_nodes.end(); node_it++)
|
|
{
|
|
update_uniform(command_buffer, *node_it->second.first, thread_index);
|
|
|
|
// Invert the front face if the mesh was flipped
|
|
const auto &scale = node_it->second.first->get_transform().get_scale();
|
|
bool flipped = scale.x * scale.y * scale.z < 0;
|
|
VkFrontFace front_face = flipped ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
|
|
draw_submesh(command_buffer, *node_it->second.second, front_face);
|
|
}
|
|
}
|
|
|
|
// Enable alpha blending
|
|
ColorBlendAttachmentState color_blend_attachment{};
|
|
color_blend_attachment.blend_enable = VK_TRUE;
|
|
color_blend_attachment.src_color_blend_factor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
color_blend_attachment.dst_color_blend_factor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
color_blend_attachment.src_alpha_blend_factor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
|
|
ColorBlendState color_blend_state{};
|
|
color_blend_state.attachments.resize(get_output_attachments().size());
|
|
for (auto &it : color_blend_state.attachments)
|
|
{
|
|
it = color_blend_attachment;
|
|
}
|
|
command_buffer.set_color_blend_state(color_blend_state);
|
|
|
|
command_buffer.set_depth_stencil_state(get_depth_stencil_state());
|
|
|
|
// Draw transparent objects in back-to-front order
|
|
{
|
|
ScopedDebugLabel transparent_debug_label{command_buffer, "Transparent objects"};
|
|
|
|
for (auto node_it = transparent_nodes.rbegin(); node_it != transparent_nodes.rend(); node_it++)
|
|
{
|
|
update_uniform(command_buffer, *node_it->second.first, thread_index);
|
|
|
|
draw_submesh(command_buffer, *node_it->second.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeometrySubpass::update_uniform(vkb::core::CommandBufferC &command_buffer, sg::Node &node, size_t thread_index)
|
|
{
|
|
GlobalUniform global_uniform;
|
|
|
|
global_uniform.camera_view_proj = camera.get_pre_rotation() * vkb::rendering::vulkan_style_projection(camera.get_projection()) * camera.get_view();
|
|
|
|
auto &render_frame = get_render_context().get_active_frame();
|
|
|
|
auto &transform = node.get_transform();
|
|
|
|
auto allocation = render_frame.allocate_buffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(GlobalUniform), thread_index);
|
|
|
|
global_uniform.model = transform.get_world_matrix();
|
|
|
|
global_uniform.camera_position = glm::vec3(glm::inverse(camera.get_view())[3]);
|
|
|
|
allocation.update(global_uniform);
|
|
|
|
command_buffer.bind_buffer(allocation.get_buffer(), allocation.get_offset(), allocation.get_size(), 0, 1, 0);
|
|
}
|
|
|
|
void GeometrySubpass::draw_submesh(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh, VkFrontFace front_face)
|
|
{
|
|
auto &device = command_buffer.get_device();
|
|
|
|
ScopedDebugLabel submesh_debug_label{command_buffer, sub_mesh.get_name().c_str()};
|
|
|
|
prepare_pipeline_state(command_buffer, front_face, sub_mesh.get_material()->double_sided);
|
|
|
|
MultisampleState multisample_state{};
|
|
multisample_state.rasterization_samples = get_sample_count();
|
|
command_buffer.set_multisample_state(multisample_state);
|
|
|
|
auto &vert_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), sub_mesh.get_shader_variant());
|
|
auto &frag_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), sub_mesh.get_shader_variant());
|
|
|
|
std::vector<ShaderModule *> shader_modules{&vert_shader_module, &frag_shader_module};
|
|
|
|
auto &pipeline_layout = prepare_pipeline_layout(command_buffer, shader_modules);
|
|
|
|
command_buffer.bind_pipeline_layout(pipeline_layout);
|
|
|
|
if (pipeline_layout.get_push_constant_range_stage(sizeof(PBRMaterialUniform)) != 0)
|
|
{
|
|
prepare_push_constants(command_buffer, sub_mesh);
|
|
}
|
|
|
|
DescriptorSetLayout &descriptor_set_layout = pipeline_layout.get_descriptor_set_layout(0);
|
|
|
|
for (auto &texture : sub_mesh.get_material()->textures)
|
|
{
|
|
if (auto layout_binding = descriptor_set_layout.get_layout_binding(texture.first))
|
|
{
|
|
command_buffer.bind_image(texture.second->get_image()->get_vk_image_view(),
|
|
texture.second->get_sampler()->vk_sampler,
|
|
0, layout_binding->binding, 0);
|
|
}
|
|
}
|
|
|
|
auto vertex_input_resources = pipeline_layout.get_resources(ShaderResourceType::Input, VK_SHADER_STAGE_VERTEX_BIT);
|
|
|
|
VertexInputState vertex_input_state;
|
|
|
|
for (auto &input_resource : vertex_input_resources)
|
|
{
|
|
sg::VertexAttribute attribute;
|
|
|
|
if (!sub_mesh.get_attribute(input_resource.name, attribute))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
VkVertexInputAttributeDescription vertex_attribute{};
|
|
vertex_attribute.binding = input_resource.location;
|
|
vertex_attribute.format = attribute.format;
|
|
vertex_attribute.location = input_resource.location;
|
|
vertex_attribute.offset = attribute.offset;
|
|
|
|
vertex_input_state.attributes.push_back(vertex_attribute);
|
|
|
|
VkVertexInputBindingDescription vertex_binding{};
|
|
vertex_binding.binding = input_resource.location;
|
|
vertex_binding.stride = attribute.stride;
|
|
|
|
vertex_input_state.bindings.push_back(vertex_binding);
|
|
}
|
|
|
|
command_buffer.set_vertex_input_state(vertex_input_state);
|
|
|
|
// Find submesh vertex buffers matching the shader input attribute names
|
|
for (auto &input_resource : vertex_input_resources)
|
|
{
|
|
const auto &buffer_iter = sub_mesh.vertex_buffers.find(input_resource.name);
|
|
|
|
if (buffer_iter != sub_mesh.vertex_buffers.end())
|
|
{
|
|
std::vector<std::reference_wrapper<const vkb::core::BufferC>> buffers;
|
|
buffers.emplace_back(std::ref(buffer_iter->second));
|
|
|
|
// Bind vertex buffers only for the attribute locations defined
|
|
command_buffer.bind_vertex_buffers(input_resource.location, std::move(buffers), {0});
|
|
}
|
|
}
|
|
|
|
draw_submesh_command(command_buffer, sub_mesh);
|
|
}
|
|
|
|
void GeometrySubpass::prepare_pipeline_state(vkb::core::CommandBufferC &command_buffer,
|
|
VkFrontFace front_face,
|
|
bool double_sided_material)
|
|
{
|
|
RasterizationState rasterization_state = base_rasterization_state;
|
|
rasterization_state.front_face = front_face;
|
|
|
|
if (double_sided_material)
|
|
{
|
|
rasterization_state.cull_mode = VK_CULL_MODE_NONE;
|
|
}
|
|
|
|
command_buffer.set_rasterization_state(rasterization_state);
|
|
|
|
MultisampleState multisample_state{};
|
|
multisample_state.rasterization_samples = get_sample_count();
|
|
command_buffer.set_multisample_state(multisample_state);
|
|
}
|
|
|
|
PipelineLayout &GeometrySubpass::prepare_pipeline_layout(vkb::core::CommandBufferC &command_buffer,
|
|
const std::vector<ShaderModule *> &shader_modules)
|
|
{
|
|
// Sets any specified resource modes
|
|
for (auto &shader_module : shader_modules)
|
|
{
|
|
for (auto &resource_mode : get_resource_mode_map())
|
|
{
|
|
shader_module->set_resource_mode(resource_mode.first, resource_mode.second);
|
|
}
|
|
}
|
|
|
|
return command_buffer.get_device().get_resource_cache().request_pipeline_layout(shader_modules);
|
|
}
|
|
|
|
void GeometrySubpass::prepare_push_constants(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh)
|
|
{
|
|
auto pbr_material = dynamic_cast<const sg::PBRMaterial *>(sub_mesh.get_material());
|
|
|
|
PBRMaterialUniform pbr_material_uniform{};
|
|
pbr_material_uniform.base_color_factor = pbr_material->base_color_factor;
|
|
pbr_material_uniform.metallic_factor = pbr_material->metallic_factor;
|
|
pbr_material_uniform.roughness_factor = pbr_material->roughness_factor;
|
|
|
|
auto data = to_bytes(pbr_material_uniform);
|
|
|
|
if (!data.empty())
|
|
{
|
|
command_buffer.push_constants(data);
|
|
}
|
|
}
|
|
|
|
void GeometrySubpass::draw_submesh_command(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh)
|
|
{
|
|
// Draw submesh indexed if indices exists
|
|
if (sub_mesh.vertex_indices != 0)
|
|
{
|
|
// Bind index buffer of submesh
|
|
command_buffer.bind_index_buffer(*sub_mesh.index_buffer, sub_mesh.index_offset, sub_mesh.index_type);
|
|
|
|
// Draw submesh using indexed data
|
|
command_buffer.draw_indexed(sub_mesh.vertex_indices, 1, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
// Draw submesh using vertices only
|
|
command_buffer.draw(sub_mesh.vertices_count, 1, 0, 0);
|
|
}
|
|
}
|
|
|
|
void GeometrySubpass::set_thread_index(uint32_t index)
|
|
{
|
|
thread_index = index;
|
|
}
|
|
} // namespace vkb
|