/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // the shadow maps of the lamps in the vulkan renderer (the stage P4 of the plan; opengl46_renderer::Render_local_shadows() and the // rest): the lamp of the occupied cab, the lamps of the scenery and the headlights of the vehicles, which gfx::local_shadows picks, // drawn by the shadow pass of the shared walk of the scene to the maps of vk14::local_shadow_maps #include "stdafx.h" #include "rendering/vulkan14/vulkan14context.h" #include "rendering/vulkan14/vulkan14commands.h" #include "model/AnimModel.h" #include "simulation/simulation.h" #include "utilities/Globals.h" #include "vehicle/DynObj.h" namespace { VkPipelineStageFlags2 const depth_stage{VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT}; VkAccessFlags2 const depth_access{VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT}; VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT}; } // namespace // the rendering of the map the maps set up last (begin(), begin_lamp(), begin_headlight()), and the pass of the walk into it: the map // made ready for it (the states of the images are tracked by the main renderer), then begun (by the main renderer, or by a walker given // the target, begin_local_shadow_target()) void vulkan14_renderer::begin_local_shadow_rendering() { m_draw->local_target = m_context->scene.lamps.target(); auto const &target{m_draw->local_target}; if ((target.map == nullptr) || (target.view == VK_NULL_HANDLE)) { return; } vk14::barriers(m_commands, {vk14::use_of(*target.map, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)}); } void vulkan14_renderer::begin_local_shadow_target() { auto &ctx{*m_context}; auto &scene{ctx.scene}; auto const &target{m_draw->local_target}; auto const commands{m_commands}; if ((target.map == nullptr) || (target.view == VK_NULL_HANDLE)) { return; } auto const use{vk14::use_of(*target.map, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)}; VkRenderingAttachmentInfo depth{}; depth.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; depth.imageView = target.view; depth.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; depth.loadOp = (target.clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD); depth.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // the farthest depth depth.clearValue.depthStencil = {0.f, 0}; VkRenderingInfo rendering{}; rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; rendering.renderArea = {{0, 0}, {target.size, target.size}}; rendering.layerCount = 1; rendering.viewMask = target.view_mask; rendering.pDepthAttachment = &depth; commands->begin_rendering(&rendering); remember_rendering(rendering, {use}); // the cube maps: one sided walls (of cabs, of buildings) face the lamp, they are to cast shadows as well. the maps of the // headlights: the back faces, as in the shadows of the sun start_draws(target.cube ? scene.cube_formats : scene.shadow_formats, static_cast(target.size), static_cast(target.size), vk14::scene_state::set_plain, (false == target.cube)); m_draw->cullface = (false == target.cube); walk_blending(false); upload_scene_block(); upload_light_header(); } void vulkan14_renderer::end_local_shadow_rendering() { if (m_draw->rendering) { m_commands->end_rendering(); } end_draws(); } // the maps in the layout the shading reads them in: the ones made or drawn since void vulkan14_renderer::ready_lamp_maps() { auto &lamps{m_context->scene.lamps}; std::vector uses; for (auto *map : {lamps.cab_map(), lamps.lamp_maps(), lamps.headlight_maps()}) { if (map != nullptr) { uses.push_back(vk14::use_of(*map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)); } } if (false == uses.empty()) { vk14::barriers(m_commands, uses); } } void vulkan14_renderer::render_local_shadows() { auto &lamps{m_context->scene.lamps}; // only the light buffers with the lamps read the maps auto const *vehicle{Global.gfx_lights_clustered ? m_locallights.cab_lamp_vehicle() : nullptr}; lamps.update_cab(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lights(), m_framestamp); if (lamps.cab_draws().empty()) { return; } m_context->passes.begin_pass("lamp shadows"); // the cab drawn by the shadow pass to the map of each light, with the programs of the cube map. the camera is put at the vehicle, // which Render_cab() places the cab relative to, and the modelview takes the cab model to the space of the map m_renderpass.draw_mode = rendermode::shadows; m_renderpass.pass_camera.position() = vehicle->GetPosition(); OpenGLMatrices.mode(GL_MODELVIEW); auto const previousnear{scene_ubs.shadow_fit.y}; for (auto const slot : lamps.cab_draws()) { m_renderpass.draw_stats = {}; OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.cab(slot).lamp}) * glm::inverse(vehicle->mMatrix)}); lamps.begin(slot); // the near plane of the map, for shadowcube.vert, in the scene block begin_local_shadow_target() uploads scene_ubs.shadow_fit.y = lamps.cab(slot).nearplane; begin_local_shadow_rendering(); begin_local_shadow_target(); m_cubeshadows = true; Render_cab(vehicle, 0.0f, false); m_cubeshadows = false; end_local_shadow_rendering(); lamps.end(static_cast(m_renderpass.draw_stats.drawcalls)); } scene_ubs.shadow_fit.y = previousnear; m_renderpass = m_colorpass; m_context->passes.end_pass(); } void vulkan14_renderer::render_lamp_shadows() { auto &lamps{m_context->scene.lamps}; m_lampscenerydrawn = 0; auto const &slots{lamps.lamps()}; if (slots.empty() || (false == Global.gfx_lights_clustered)) { return; } // vehicles near the camera cast the shadows of the lamps, and the occupied cab lamps.lamp_vehicles(Global.pCamera.Pos); auto const vehiclesrange{static_cast(Global.gfx_lights_shadows_vehicles_range)}; auto *cab{vehiclesrange > 0.0 ? lamps.shadow_cab() : nullptr}; auto timing{false}; for (auto const idx : slots) { auto const &slot{lamps.slot(idx)}; if (lamps.scenery_wanted(idx)) { if (false == timing) { m_context->passes.begin_pass("scenery lamp shadows"); lamps.lamps_timer_begin(); timing = true; } render_lamp_shadow(idx, false, nullptr); } if (false == slot.scenery) { continue; } // the vehicles by the lamp, within its reach auto const &slotvehicles{lamps.slot_vehicles(slot)}; auto *slotcab{(cab != nullptr) && (glm::length(cab->GetPosition() - slot.position) <= slot.range + cab->radius()) ? cab : nullptr}; if (slotvehicles.empty() && (slotcab == nullptr)) { lamps.no_vehicles(idx); continue; } if (false == lamps.vehicles_wanted(idx, m_framestamp)) { continue; } if (false == timing) { m_context->passes.begin_pass("scenery lamp shadows"); lamps.lamps_timer_begin(); timing = true; } render_lamp_shadow(idx, true, slotcab); } if (timing) { lamps.lamps_timer_end(); m_context->passes.end_pass(); } } void vulkan14_renderer::render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab) { auto &lamps{m_context->scene.lamps}; auto const &slot{lamps.slot(Slot)}; // the shadow pass from the lamp, in world axes: the camera of the pass is the lamp, its frustum the box of its reach. the // distances which pick the details of the models are measured from the real camera, as in the shadows of the sun m_renderpass = m_colorpass; m_renderpass.draw_mode = rendermode::shadows; m_renderpass.draw_range = slot.range; m_renderpass.draw_stats = {}; auto &camera{m_renderpass.pass_camera}; camera.position() = slot.position; camera.modelview() = glm::mat4{glm::translate(glm::dmat4{1.0}, -slot.position)}; auto const reach{slot.range}; camera.projection() = glm::ortho(-reach, reach, -reach, reach, -reach, reach); camera.update_frustum(); OpenGLMatrices.mode(GL_MODELVIEW); OpenGLMatrices.load_matrix(glm::mat4{1.f}); lamps.begin_lamp(Slot, Vehicles, m_instancerecordsversion, m_framestamp); begin_local_shadow_rendering(); // the vehicles by the lamp; or the scenery, with its instances culled on the gpu, each scenery part drawn in a frame with a culling // of its own std::vector vehicles; vk14::instance_cull *cull{nullptr}; if (Vehicles) { vehicles = lamps.slot_vehicles(); } else { cull = light_shadow_cull(m_context->scene.cull.lamps, m_lampscenerydrawn++, Global.gfx_gpucull_shadows_lamps); } // the model of the lamp is left out, unless its shadows are wanted (shadeself:): what is within the near plane of the map (the // radius of the light) casts none even then auto const *owner{slot.shade_self ? nullptr : static_cast(slot.owner)}; auto const nearplane{slot.nearplane}; if (walks_wanted()) { // walked by a walker (gfx.vulkan.parallelpasses), its draws counted once it's done fork_walk([target = m_draw->local_target, Vehicles, vehicles, Cab, owner, cull, nearplane](vulkan14_renderer &Walker) { Walker.m_draw->local_target = target; Walker.walk_lamp(Vehicles, vehicles, Cab, owner, cull, nearplane); }, walk_kind::lamp); lamps.end_lamp(0); } else { walk_lamp(Vehicles, vehicles, Cab, owner, cull, nearplane); lamps.end_lamp(static_cast(m_renderpass.draw_stats.drawcalls)); } m_renderpass = m_colorpass; } // the walk of the map of a lamp set up by render_lamp_shadow() void vulkan14_renderer::walk_lamp(bool const Vehicles, std::vector const &Dynamics, TDynamicObject *Cab, TAnimModel const *Owner, vk14::instance_cull *Cull, float const Near) { // the near plane of the map, for shadowcube.vert, in the scene block begin_local_shadow_target() uploads auto const previousnear{scene_ubs.shadow_fit.y}; scene_ubs.shadow_fit.y = Near; begin_local_shadow_target(); m_cubeshadows = true; if (Vehicles) { for (auto *vehicle : Dynamics) { Render(vehicle); } if (Cab != nullptr) { Render_cab(Cab, 0.0f, false); } } else { m_cubescenery = true; m_cubeskipinstance = Owner; m_lightshadowcull = Cull; walk_region(simulation::Region, false); m_lightshadowcull = nullptr; m_cubeskipinstance = nullptr; m_cubescenery = false; } m_cubeshadows = false; end_local_shadow_rendering(); scene_ubs.shadow_fit.y = previousnear; } void vulkan14_renderer::render_headlight_shadows() { auto &lamps{m_context->scene.lamps}; // the headlights which shine, the most important first (in the order of update_lights()), whose beam may be seen within the // reach of the shadows auto const &picked{lamps.pick_headlights(simulation::Lights, m_colorpass.pass_camera)}; lamps.update_headlights(picked, m_framestamp); auto const &slots{lamps.headlights()}; if (slots.empty()) { return; } // the occupied cab, as in render_lamp_shadows() auto *cab{lamps.shadow_cab()}; auto timing{false}; for (auto const idx : slots) { auto const &slot{lamps.headlight(idx)}; light_array::light_record const *light{nullptr}; for (auto const *candidate : picked) { if ((candidate->owner == slot.owner) && (candidate->index == slot.index)) { light = candidate; break; } } if (light == nullptr) { continue; } glm::dvec3 projector; glm::mat3 rotation; glm::mat4 projection; float nearplane, fieldofview; headlight_projector(*light, projector, rotation, projection, nearplane, fieldofview); // a vehicle on the move needs its map anew, the things around it and the vehicles passing by only now and then auto const moved{(glm::length(projector - slot.projector) > 0.01) || (glm::dot(light->direction, slot.direction) < 0.99999f)}; if (false == lamps.headlight_wanted(idx, moved, m_framestamp)) { continue; } if (false == timing) { m_context->passes.begin_pass("headlight shadows"); lamps.headlights_timer_begin(); timing = true; } render_headlight_shadow(idx, *light, cab); } if (timing) { lamps.headlights_timer_end(); m_context->passes.end_pass(); } } void vulkan14_renderer::render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab) { auto &lamps{m_context->scene.lamps}; glm::dvec3 projector; glm::mat3 rotation; glm::mat4 projection; float nearplane, fieldofview; headlight_projector(Light, projector, rotation, projection, nearplane, fieldofview); auto const reach{nearplane + Global.gfx_lights_shadows_headlights_range}; // the shadow pass from the projector of the beam, with its projection. the distances which pick the details of the models // are measured from the real camera, as in the shadows of the sun m_renderpass = m_colorpass; m_renderpass.draw_mode = rendermode::shadows; m_renderpass.draw_range = reach; m_renderpass.draw_stats = {}; auto &camera{m_renderpass.pass_camera}; camera.position() = projector; camera.modelview() = glm::mat4{glm::dmat4{glm::dmat3{rotation}} * glm::translate(glm::dmat4{1.0}, -projector)}; camera.projection() = projection; // the projection goes on to the infinity, what the shadows are cast by ends at their reach camera.update_frustum(glm::perspective(fieldofview, 1.f, nearplane, reach)); setup_matrices(); auto const colorprojection{scene_ubs.projection}; scene_ubs.projection = projection; lamps.begin_headlight(Slot, projector, rotation, projection, Light.direction, m_framestamp); begin_local_shadow_rendering(); // the instances culled on the gpu, each slot with a culling of its own; the occupied cab, within the reach auto *cull{light_shadow_cull(m_context->scene.cull.headlights, Slot, Global.gfx_gpucull_shadows_headlights)}; auto *cab{((Cab != nullptr) && (Cab != Light.owner) && (glm::length(Cab->GetPosition() - projector) <= reach + Cab->radius())) ? Cab : nullptr}; auto const *owner{Light.owner}; if (walks_wanted()) { // walked by a walker (gfx.vulkan.parallelpasses), its draws counted once it's done fork_walk([target = m_draw->local_target, owner, cab, cull](vulkan14_renderer &Walker) { Walker.m_draw->local_target = target; Walker.walk_headlight(owner, cab, cull); }, walk_kind::headlight); lamps.end_headlight(0); } else { walk_headlight(owner, cab, cull); lamps.end_headlight(static_cast(m_renderpass.draw_stats.drawcalls)); } scene_ubs.projection = colorprojection; m_renderpass = m_colorpass; } // the walk of the map of a set of headlights set up by render_headlight_shadow() void vulkan14_renderer::walk_headlight(TDynamicObject const *Owner, TDynamicObject *Cab, vk14::instance_cull *Cull) { // the matrices of the projector (the walker's own, made from the pass it was given) setup_matrices(); begin_local_shadow_target(); m_shadowskipvehicle = Owner; m_lightshadowcull = Cull; walk_region(simulation::Region, false); m_lightshadowcull = nullptr; if (Cab != nullptr) { Render_cab(Cab, 0.0f, false); } m_shadowskipvehicle = nullptr; end_local_shadow_rendering(); } vk14::instance_cull *vulkan14_renderer::light_shadow_cull(std::vector> &Culls, std::size_t const Index, bool const Enabled) { auto &ctx{*m_context}; auto &cull{ctx.scene.cull}; // the same conditions as those of the other passes in walk_region_begin() if ((Global.gfx_gpucull == 0) || (false == Enabled) || (m_current_viewport == nullptr) || (false == m_current_viewport->main) || (false == cull.ready)) { return nullptr; } if (Culls.size() <= Index) { Culls.resize(Index + 1); } if (!Culls[Index]) { // the buffers of its own, the records and the kernels of the main view auto culling{std::make_unique()}; vk14::cull_environment environment; if (false == culling->init(environment, ctx.device.handle(), ctx.pipelines, ctx.shaders, ctx.scene.pass_layout, &cull.main)) { culling->destroy(ctx.device.handle()); return nullptr; } Culls[Index] = std::move(culling); } return Culls[Index].get(); }