mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 21:37:31 +01:00
Vulkan: parity with GL (P1–P7), shared GPU culling records (N6c), threaded command recording and render thread (M1, M4)
This commit is contained in:
@@ -310,7 +310,17 @@ set(SOURCES
|
||||
"gl/glsl_common.cpp"
|
||||
"rendering/common/shadersource.cpp"
|
||||
"rendering/common/scenewalk.cpp"
|
||||
"rendering/common/scenewalkindirect.cpp"
|
||||
"rendering/common/instancecull.cpp"
|
||||
"rendering/common/shadowfit.cpp"
|
||||
"rendering/common/localshadows.cpp"
|
||||
"rendering/common/terrainmaterials.cpp"
|
||||
"rendering/common/grassfield.cpp"
|
||||
"rendering/common/gpuparticles.cpp"
|
||||
"rendering/common/locallights.cpp"
|
||||
"rendering/common/skylight.cpp"
|
||||
"rendering/common/weatherstate.cpp"
|
||||
"rendering/common/exposure.cpp"
|
||||
"gl/buffer.cpp"
|
||||
"gl/fence.cpp"
|
||||
"gl/pbo.cpp"
|
||||
@@ -430,6 +440,7 @@ if (WITH_VULKAN)
|
||||
"rendering/vulkan14/vulkan14vma.cpp"
|
||||
"rendering/vulkan14/vulkan14shaders.cpp"
|
||||
"rendering/vulkan14/vulkan14descriptors.cpp"
|
||||
"rendering/vulkan14/vulkan14compute.cpp"
|
||||
"rendering/vulkan14/vulkan14passes.cpp"
|
||||
"rendering/vulkan14/vulkan14texture.cpp"
|
||||
"rendering/vulkan14/vulkan14imgui.cpp"
|
||||
@@ -437,6 +448,18 @@ if (WITH_VULKAN)
|
||||
"rendering/vulkan14/vulkan14geometry.cpp"
|
||||
"rendering/vulkan14/vulkan14scene.cpp"
|
||||
"rendering/vulkan14/vulkan14frame.cpp"
|
||||
"rendering/vulkan14/vulkan14sky.cpp"
|
||||
"rendering/vulkan14/vulkan14weather.cpp"
|
||||
"rendering/vulkan14/vulkan14ao.cpp"
|
||||
"rendering/vulkan14/vulkan14cull.cpp"
|
||||
"rendering/vulkan14/vulkan14indirect.cpp"
|
||||
"rendering/vulkan14/vulkan14shadows.cpp"
|
||||
"rendering/vulkan14/vulkan14lamps.cpp"
|
||||
"rendering/vulkan14/vulkan14terrain.cpp"
|
||||
"rendering/vulkan14/vulkan14grass.cpp"
|
||||
"rendering/vulkan14/vulkan14particles.cpp"
|
||||
"rendering/vulkan14/vulkan14debug.cpp"
|
||||
"rendering/vulkan14/vulkan14commands.cpp"
|
||||
"ref/volk/volk.c")
|
||||
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")
|
||||
|
||||
|
||||
95
rendering/common/exposure.cpp
Normal file
95
rendering/common/exposure.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/exposure.h"
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
|
||||
float const HISTOGRAM_LOG_MIN{-12.f};
|
||||
float const HISTOGRAM_LOG_MAX{6.f};
|
||||
// the bloom chain goes down this many levels at most, and not below this size
|
||||
std::size_t const BLOOM_LEVELS{6};
|
||||
int const BLOOM_MIN_SIZE{8};
|
||||
// soft knee of the threshold, a fraction of it
|
||||
float const BLOOM_KNEE{0.5f};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
glm::vec2 exposure_settings::histogram_range()
|
||||
{
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
return glm::vec2{HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN)};
|
||||
}
|
||||
|
||||
glm::vec2 exposure_settings::average_range()
|
||||
{
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
return glm::vec2{HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins};
|
||||
}
|
||||
|
||||
glm::vec4 exposure_settings::adaptation(double const Deltatime)
|
||||
{
|
||||
return glm::vec4{static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)), Global.gfx_postfx_autoexposure_speed_dark, Global.gfx_postfx_autoexposure_speed_bright, Global.gfx_postfx_autoexposure_key};
|
||||
}
|
||||
|
||||
glm::vec2 exposure_settings::exposure_range()
|
||||
{
|
||||
return glm::vec2{std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max)};
|
||||
}
|
||||
|
||||
std::vector<glm::ivec2> exposure_settings::bloom_sizes(int const Width, int const Height)
|
||||
{
|
||||
std::vector<glm::ivec2> sizes;
|
||||
glm::ivec2 size{Width, Height};
|
||||
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
|
||||
{
|
||||
size = glm::max(size / 2, glm::ivec2{1});
|
||||
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
|
||||
break;
|
||||
sizes.push_back(size);
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
glm::vec4 exposure_settings::bloom_params(std::size_t const Level)
|
||||
{
|
||||
return glm::vec4{(Level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
|
||||
}
|
||||
|
||||
glm::vec4 exposure_settings::tonemap_params(std::size_t const Bloomlevels)
|
||||
{
|
||||
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
|
||||
return glm::vec4{bloom, Global.gfx_postfx_autoexposure ? 1.f : 0.f, std::exp2(Global.gfx_postfx_autoexposure_compensation), Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
|
||||
}
|
||||
|
||||
void exposure_settings::draw_debug_ui(float const *Readback)
|
||||
{
|
||||
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
|
||||
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
|
||||
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
|
||||
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
|
||||
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", Readback[2], Readback[0], Readback[1], std::log2(std::max(Readback[1], 1e-6f)));
|
||||
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
|
||||
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
|
||||
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
44
rendering/common/exposure.h
Normal file
44
rendering/common/exposure.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
// what the automatic exposure and the bloom of the renderers share (the part of the stage S3 of
|
||||
// claude/gl46-renderer-features-plan.md which isn't the work of the gpu, taken out of opengl46_exposure for the vulkan renderer,
|
||||
// stage P1 of claude/vulkan14-renderer-plan.md): the parameters of the histogram and of the adaptation
|
||||
// (exposure_histogram.comp, exposure_average.comp), the sizes of the levels of the bloom chain and the parameters of its steps
|
||||
// (postfx_bloom_*.frag), the parameters of the tonemapping and the controls of the debug window
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
struct exposure_settings
|
||||
{
|
||||
// x: log2 of the lowest luminance binned, y: bins per unit of log2 luminance (exposure_histogram.comp)
|
||||
static glm::vec2 histogram_range();
|
||||
// x: log2 of the lowest luminance binned, y: log2 luminance per bin (exposure_average.comp)
|
||||
static glm::vec2 average_range();
|
||||
// x: time since the last measurement (Deltatime, s), y, z: rates of the adaptation to a darker and a brighter image, w: key
|
||||
static glm::vec4 adaptation(double const Deltatime);
|
||||
// the lowest and the highest exposure (multipliers)
|
||||
static glm::vec2 exposure_range();
|
||||
// the sizes of the levels of the bloom chain of an image, half the size each, as many as there are (empty: no chain)
|
||||
static std::vector<glm::ivec2> bloom_sizes(int const Width, int const Height);
|
||||
// param[0] of a step down the chain (postfx_bloom_down.frag): Level 0 is the first one, from the image itself
|
||||
static glm::vec4 bloom_params(std::size_t const Level);
|
||||
// parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none)
|
||||
static glm::vec4 tonemap_params(std::size_t const Bloomlevels);
|
||||
// controls of the debug window; Readback: the adapted luminance, the exposure and the average luminance the gpu worked out
|
||||
static void draw_debug_ui(float const *Readback);
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
321
rendering/common/gpuparticles.cpp
Normal file
321
rendering/common/gpuparticles.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/gpuparticles.h"
|
||||
|
||||
#include "rendering/particles.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
|
||||
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
|
||||
// the origin of the smoke moves by whole steps when the camera gets this far from it
|
||||
double constexpr ORIGIN_STEP{256.0};
|
||||
double constexpr ORIGIN_REACH{1024.0};
|
||||
// how much the smoke scatters the light onwards (Henyey-Greenstein)
|
||||
float constexpr SMOKE_FORWARD{0.3f};
|
||||
|
||||
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
|
||||
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
|
||||
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
|
||||
|
||||
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
|
||||
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
|
||||
struct precipitation_kind
|
||||
{
|
||||
float fall;
|
||||
float exposure;
|
||||
float opacity;
|
||||
float forward;
|
||||
float brightness;
|
||||
};
|
||||
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
|
||||
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
|
||||
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
|
||||
double constexpr WIND_SHARE{0.5};
|
||||
// the camera faster than this from a frame to the next was moved, not driven or flown
|
||||
double constexpr CAMERA_JUMP{120.0};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool gpu_particles::init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
if (false == gpu_init())
|
||||
return false;
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gpu_particles::smoke_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
bool gpu_particles::precipitation_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
void gpu_particles::clear()
|
||||
{
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_poolfull = false;
|
||||
m_sourcerecords.clear();
|
||||
m_places = 0;
|
||||
m_storage = false;
|
||||
gpu_clear();
|
||||
m_precipitationcapacity = 0;
|
||||
m_originset = false;
|
||||
m_lastupdate = 0;
|
||||
}
|
||||
|
||||
void gpu_particles::layout(particle_manager &Particles)
|
||||
{
|
||||
auto const &sources{Particles.sequence()};
|
||||
// the sources already laid out which asked for the same places since
|
||||
std::size_t kept{0};
|
||||
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
|
||||
++kept;
|
||||
if ((kept == m_requested.size()) && (kept == sources.size()) && m_storage)
|
||||
return;
|
||||
if (kept < m_requested.size())
|
||||
{
|
||||
// a source went away or changed: all anew
|
||||
kept = 0;
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_places = 0;
|
||||
m_poolfull = false;
|
||||
}
|
||||
auto const oldplaces{m_places};
|
||||
|
||||
auto first{m_places};
|
||||
m_pool.resize(sources.size());
|
||||
m_requested.resize(sources.size());
|
||||
for (std::size_t i = kept; i < sources.size(); ++i)
|
||||
{
|
||||
m_requested[i] = sources[i].max_particles();
|
||||
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
|
||||
m_pool[i] = pool_source{};
|
||||
if (first + size > MAX_SMOKE_PLACES)
|
||||
{
|
||||
if (false == m_poolfull)
|
||||
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
|
||||
m_poolfull = true;
|
||||
continue;
|
||||
}
|
||||
m_pool[i].first = first;
|
||||
m_pool[i].size = size;
|
||||
first += size;
|
||||
}
|
||||
m_places = first;
|
||||
|
||||
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
|
||||
for (std::size_t i = 0; i < m_pool.size(); ++i)
|
||||
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
|
||||
|
||||
// the particles of the places kept go over to the new storage, the new places are dead
|
||||
auto const places{std::max<std::uint32_t>(m_places, 1)};
|
||||
gpu_layout(places, (m_storage && (oldplaces > 0) && (kept > 0)) ? oldplaces : 0, owners, sources.size());
|
||||
m_storage = true;
|
||||
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
|
||||
}
|
||||
|
||||
void gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
|
||||
{
|
||||
if (false == init())
|
||||
return;
|
||||
layout(Particles);
|
||||
if (Particles.updates() == m_lastupdate)
|
||||
return;
|
||||
m_lastupdate = Particles.updates();
|
||||
if (m_places == 0)
|
||||
return;
|
||||
|
||||
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
|
||||
glm::vec3 shift{0.f};
|
||||
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
|
||||
if (false == m_originset)
|
||||
{
|
||||
m_origin = snapped;
|
||||
m_originset = true;
|
||||
}
|
||||
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
|
||||
{
|
||||
shift = glm::vec3{m_origin - snapped};
|
||||
m_origin = snapped;
|
||||
}
|
||||
|
||||
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
|
||||
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
|
||||
auto const &sources{Particles.sequence()};
|
||||
m_smokealive = 0;
|
||||
for (std::size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
auto const &state{sources[i].gpu()};
|
||||
auto &pool{m_pool[i]};
|
||||
auto &record{m_sourcerecords[i]};
|
||||
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
|
||||
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
|
||||
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
|
||||
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
|
||||
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
|
||||
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
|
||||
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], state.spread};
|
||||
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
|
||||
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
|
||||
record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, state.fadein};
|
||||
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
if (pool.size > 0)
|
||||
{
|
||||
pool.head = (pool.head + spawns) % pool.size;
|
||||
if (spawns > 0)
|
||||
m_smokealive += pool.size;
|
||||
}
|
||||
}
|
||||
|
||||
smoke_simulation simulation;
|
||||
simulation.places = m_places;
|
||||
simulation.step = glm::vec4{static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z};
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
simulation.wind = glm::vec4{wind.x, wind.y, wind.z, 0.f};
|
||||
simulation.seed = static_cast<std::uint32_t>(m_lastupdate);
|
||||
gpu_simulate(simulation, m_sourcerecords);
|
||||
}
|
||||
|
||||
std::array<glm::vec4, 5> gpu_particles::planes(view const &View)
|
||||
{
|
||||
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
|
||||
auto const matrix{glm::transpose(View.projection * View.modelview)};
|
||||
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
|
||||
for (auto &plane : result)
|
||||
{
|
||||
auto const length{glm::length(glm::vec3{plane})};
|
||||
if (length > 0.f)
|
||||
plane /= length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::size_t gpu_particles::render_smoke(view const &View)
|
||||
{
|
||||
if ((false == init()) || (m_places == 0))
|
||||
return 0;
|
||||
smoke_draw draw;
|
||||
draw.places = m_places;
|
||||
draw.origin = glm::vec3{m_origin - View.camera};
|
||||
draw.view = View.modelview;
|
||||
draw.options = glm::vec4{Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f};
|
||||
draw.planes = planes(View);
|
||||
draw.soft = glm::vec4{View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f};
|
||||
gpu_render_smoke(draw, m_smokedrawn);
|
||||
return m_smokedrawn;
|
||||
}
|
||||
|
||||
std::size_t gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
|
||||
{
|
||||
if (false == init())
|
||||
return 0;
|
||||
gpu_collect_precipitation(m_precipitationdrawn);
|
||||
|
||||
auto const &kind{Snow ? SNOW : RAIN};
|
||||
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
|
||||
if ((false == m_framestarted) || (Frame != m_frame))
|
||||
{
|
||||
m_framestarted = true;
|
||||
m_frame = Frame;
|
||||
auto const seconds{Timer::GetDeltaTime()};
|
||||
if (seconds > 0.0)
|
||||
{
|
||||
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
|
||||
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
|
||||
}
|
||||
m_time = std::fmod(m_time + seconds, 3600.0);
|
||||
if (m_camerastarted)
|
||||
{
|
||||
auto const velocity{(View.camera - m_lastcamera) / seconds};
|
||||
if (glm::length(velocity) < CAMERA_JUMP)
|
||||
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
|
||||
}
|
||||
}
|
||||
m_lastcamera = View.camera;
|
||||
m_camerastarted = true;
|
||||
}
|
||||
|
||||
// the particles, more of them in heavier rain or snow
|
||||
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
|
||||
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
|
||||
m_precipitationcount = count;
|
||||
m_precipitationsnow = Snow;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (m_precipitationcapacity < count)
|
||||
{
|
||||
m_precipitationcapacity = std::max(count, most);
|
||||
gpu_precipitation_storage(m_precipitationcapacity);
|
||||
}
|
||||
|
||||
precipitation_draw draw;
|
||||
draw.count = count;
|
||||
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
|
||||
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
|
||||
draw.counts = glm::uvec4{second, third, count, Snow ? 1u : 0u};
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
draw.offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
|
||||
}
|
||||
draw.sizes = glm::vec4{BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f};
|
||||
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
|
||||
draw.velocity = glm::vec4{velocity.x, velocity.y, velocity.z, kind.fall};
|
||||
draw.timing = glm::vec4{static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity)};
|
||||
draw.view = View.modelview;
|
||||
draw.options = glm::vec4{1.f, kind.forward, kind.brightness, 0.f};
|
||||
draw.planes = planes(View);
|
||||
// the width of a pixel a metre in front of the camera, for the least width of a streak
|
||||
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
|
||||
draw.soft = glm::vec4{View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f};
|
||||
draw.pixel = glm::vec4{pixel, 0.f, 0.f, 0.f};
|
||||
gpu_render_precipitation(draw);
|
||||
return m_precipitationdrawn;
|
||||
}
|
||||
|
||||
std::string gpu_particles::info() const
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return {};
|
||||
std::string text;
|
||||
if (m_places > 0)
|
||||
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
|
||||
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
|
||||
if (m_precipitationcount > 0)
|
||||
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
|
||||
+ " drawn\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
185
rendering/common/gpuparticles.h
Normal file
185
rendering/common/gpuparticles.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
|
||||
class particle_manager;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// particles of the gpu, whichever api draws them: the smoke and the rain or snow.
|
||||
//
|
||||
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
|
||||
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
|
||||
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
|
||||
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
|
||||
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
|
||||
//
|
||||
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
|
||||
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
|
||||
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
|
||||
//
|
||||
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
|
||||
// their precision far from the centre of the scenery. what the passes are given is worked out here; the buffers, the compute
|
||||
// passes and the draws are the renderer's (the gpu_ functions)
|
||||
class gpu_particles
|
||||
{
|
||||
public:
|
||||
gpu_particles() = default;
|
||||
virtual ~gpu_particles() = default;
|
||||
gpu_particles(gpu_particles const &) = delete;
|
||||
gpu_particles &operator=(gpu_particles const &) = delete;
|
||||
|
||||
// what a colour pass draws with
|
||||
struct view
|
||||
{
|
||||
glm::mat4 modelview; // world axes around the camera to the view space of the pass
|
||||
glm::mat4 projection;
|
||||
glm::dvec3 camera;
|
||||
int height{0}; // of the target, pixels
|
||||
bool soft{false}; // the resolved depth of the scene drawn so far is bound to the depth of the scene of the pass
|
||||
};
|
||||
|
||||
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
|
||||
bool smoke_available();
|
||||
bool precipitation_available();
|
||||
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
|
||||
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
|
||||
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
|
||||
std::size_t render_smoke(view const &View);
|
||||
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
|
||||
// returns: particles drawn, read back
|
||||
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
|
||||
// drops what is on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
|
||||
std::string info() const;
|
||||
|
||||
protected:
|
||||
// the uniforms of particles_smoke_simulate.comp
|
||||
struct smoke_simulation
|
||||
{
|
||||
std::uint32_t places{0};
|
||||
glm::vec4 step{0.f}; // x: seconds of the update, yzw: added to the positions, as the origin moved
|
||||
glm::vec4 wind{0.f};
|
||||
std::uint32_t seed{0};
|
||||
};
|
||||
// the uniforms of particles_smoke_prepare.comp and of the draw
|
||||
struct smoke_draw
|
||||
{
|
||||
std::uint32_t places{0};
|
||||
glm::vec3 origin{0.f}; // of the particles less the camera
|
||||
glm::mat4 view{1.f};
|
||||
glm::vec4 options{0.f}; // x: lit by the lights of the pass, y: scattering onwards, z: places for the draws
|
||||
std::array<glm::vec4, 5> planes{};
|
||||
glm::vec4 soft{0.f}; // of particles_smoke.vert
|
||||
};
|
||||
// the uniforms of particles_precipitation.comp and of the draw
|
||||
struct precipitation_draw
|
||||
{
|
||||
glm::uvec4 counts{0u};
|
||||
std::array<glm::vec4, 3> offsets{};
|
||||
glm::vec4 sizes{0.f};
|
||||
glm::vec4 velocity{0.f};
|
||||
glm::vec4 timing{0.f};
|
||||
glm::mat4 view{1.f};
|
||||
glm::vec4 options{0.f};
|
||||
std::array<glm::vec4, 5> planes{};
|
||||
glm::vec4 soft{0.f}; // of particles_precipitation.vert
|
||||
glm::vec4 pixel{0.f};
|
||||
std::uint32_t count{0}; // particles, the threads of the compute pass
|
||||
};
|
||||
|
||||
// the programs; false: the particles of the gpu can't be used
|
||||
virtual bool gpu_init() = 0;
|
||||
// new storage of the smoke: Places places (at least 1) of particles, all dead but the first Kept of the storage before, which
|
||||
// keep what they had; the storage of the draws for as many particles, the source of every place (Owners) and room for the
|
||||
// records of Sources sources
|
||||
virtual void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) = 0;
|
||||
// the records of the sources (when there are any) and the compute pass of the simulation
|
||||
virtual void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) = 0;
|
||||
// the compute pass and the draw of the smoke the view sees; Drawn: the particles drawn, as the last read back gives them (left
|
||||
// as it is when nothing came back yet)
|
||||
virtual void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) = 0;
|
||||
// the particles of the rain or snow drawn, as the last read back gives them
|
||||
virtual void gpu_collect_precipitation(std::size_t &Drawn) = 0;
|
||||
// room for the draws of Capacity particles of the rain or snow
|
||||
virtual void gpu_precipitation_storage(std::uint32_t const Capacity) = 0;
|
||||
// the compute pass and the draw of the rain or snow
|
||||
virtual void gpu_render_precipitation(precipitation_draw const &Draw) = 0;
|
||||
// the storage goes (the draw commands and the read backs stay)
|
||||
virtual void gpu_clear() = 0;
|
||||
|
||||
std::uint32_t places() const
|
||||
{
|
||||
return m_places;
|
||||
}
|
||||
std::uint32_t precipitation_count() const
|
||||
{
|
||||
return m_precipitationcount;
|
||||
}
|
||||
bool precipitation_snow() const
|
||||
{
|
||||
return m_precipitationsnow;
|
||||
}
|
||||
|
||||
private:
|
||||
struct pool_source
|
||||
{
|
||||
std::uint32_t first{0};
|
||||
std::uint32_t size{0};
|
||||
std::uint32_t head{0};
|
||||
};
|
||||
|
||||
bool init();
|
||||
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
|
||||
// the particles stay; otherwise all is laid out anew
|
||||
void layout(particle_manager &Particles);
|
||||
// the planes of the view of the pass, for the culling of the compute passes
|
||||
static std::array<glm::vec4, 5> planes(view const &View);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
|
||||
// smoke
|
||||
std::vector<pool_source> m_pool;
|
||||
std::vector<std::size_t> m_requested; // the sizes the sources asked for
|
||||
std::vector<gl::particle_smoke_source> m_sourcerecords;
|
||||
std::uint32_t m_places{0};
|
||||
bool m_poolfull{false}; // some sources got no places
|
||||
bool m_storage{false}; // the storage of the smoke was made
|
||||
std::uint64_t m_lastupdate{0};
|
||||
glm::dvec3 m_origin{0.0};
|
||||
bool m_originset{false};
|
||||
std::size_t m_smokedrawn{0};
|
||||
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
|
||||
|
||||
// rain and snow
|
||||
std::uint32_t m_precipitationcapacity{0};
|
||||
unsigned int m_frame{0};
|
||||
bool m_framestarted{false};
|
||||
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
|
||||
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
|
||||
double m_time{0.0}; // seconds, for the flutter of the flakes
|
||||
glm::dvec3 m_lastcamera{0.0};
|
||||
glm::dvec3 m_cameravelocity{0.0};
|
||||
bool m_camerastarted{false};
|
||||
std::size_t m_precipitationdrawn{0};
|
||||
std::uint32_t m_precipitationcount{0};
|
||||
bool m_precipitationsnow{false};
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
269
rendering/common/grassfield.cpp
Normal file
269
rendering/common/grassfield.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/grassfield.h"
|
||||
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Timer.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr max_blades{65536}; // drawn in a tile at most
|
||||
float constexpr blade_reach{1.5f}; // metres the bounds of a tile reach above its ground, for the blades
|
||||
|
||||
// the share of the blades drawn at specified distance: fewer further away, for speed (from Thinning), and fewer and fewer from
|
||||
// Fade to Reach, where the grass ends; the same as lod_share() of grass.vert
|
||||
float lod_share(float const Distance, float const Thinning, float const Fade, float const Reach)
|
||||
{
|
||||
auto const share{Distance <= Thinning ? 1.f : std::pow(Thinning / Distance, 1.5f)};
|
||||
auto const t{std::clamp((Distance - Fade) / std::max(Reach - Fade, 1e-3f), 0.f, 1.f)};
|
||||
return share * (1.f - t * t * (3.f - 2.f * t));
|
||||
}
|
||||
|
||||
// the grass through the year, by the day of the year: x how dry it is, y how fresh (the green of spring), z how grown (share
|
||||
// of the height). the turns follow the seasons of the textures (world_environment::compute_season(): winter till day 65,
|
||||
// spring till 158, summer till 252, autumn till 341): dormant and dry in winter, greening up and growing through spring,
|
||||
// fresh in May, drying out from August, the dry grass lying lower towards winter
|
||||
glm::vec3 grass_season(int const Yearday)
|
||||
{
|
||||
struct point
|
||||
{
|
||||
float day;
|
||||
glm::vec3 value;
|
||||
};
|
||||
static point const year[]{{1.f, {0.80f, 0.f, 0.55f}}, {65.f, {0.80f, 0.f, 0.55f}}, {100.f, {0.45f, 0.3f, 0.70f}}, {135.f, {0.05f, 1.f, 0.90f}},
|
||||
{170.f, {0.f, 0.4f, 1.f}}, {200.f, {0.10f, 0.f, 1.f}}, {240.f, {0.35f, 0.f, 1.f}}, {280.f, {0.50f, 0.f, 0.95f}},
|
||||
{320.f, {0.65f, 0.f, 0.80f}}, {341.f, {0.80f, 0.f, 0.60f}}, {367.f, {0.80f, 0.f, 0.55f}}};
|
||||
auto const day{std::clamp(static_cast<float>(Yearday), 1.f, 366.f)};
|
||||
for (std::size_t i = 1; i < std::size(year); ++i)
|
||||
if (day <= year[i].day)
|
||||
{
|
||||
auto const t{(day - year[i - 1].day) / (year[i].day - year[i - 1].day)};
|
||||
return glm::mix(year[i - 1].value, year[i].value, t * t * (3.f - 2.f * t));
|
||||
}
|
||||
return year[std::size(year) - 1].value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void grass_field::release(std::uint64_t const Id, chunk &Chunk)
|
||||
{
|
||||
if (Chunk.uploaded)
|
||||
gpu_release(Id);
|
||||
Chunk.uploaded = false;
|
||||
}
|
||||
|
||||
void grass_field::clear()
|
||||
{
|
||||
for (auto &entry : m_chunks)
|
||||
release(entry.first, entry.second);
|
||||
m_chunks.clear();
|
||||
}
|
||||
|
||||
void grass_field::upload(std::uint64_t const Id, chunk &Chunk, gfx::grass_chunk const &Source)
|
||||
{
|
||||
release(Id, Chunk);
|
||||
Chunk.version = Source.version;
|
||||
Chunk.corner = Source.corner;
|
||||
Chunk.size = Source.size;
|
||||
Chunk.cells = Source.cells;
|
||||
Chunk.samples = Source.samples;
|
||||
Chunk.kinds = Source.kinds;
|
||||
gpu_upload(Id, Source);
|
||||
Chunk.uploaded = true;
|
||||
|
||||
auto const side{Source.cells + 1};
|
||||
auto const samples{Source.samples + 1};
|
||||
auto const plane{static_cast<std::size_t>(samples) * samples};
|
||||
std::size_t kinds{0};
|
||||
while (kinds < Source.kinds.size() && Source.kinds[kinds] >= 0)
|
||||
++kinds;
|
||||
|
||||
// the ground and the densest place of every kind of each tile, to skip the empty tiles and to draw as many blades as needed
|
||||
auto const spacing{static_cast<double>(Source.size) / Source.cells};
|
||||
auto const step{static_cast<double>(Source.size) / Source.samples};
|
||||
auto const tilesize{static_cast<double>(Source.size) / tiles};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto &part{Chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const range = [](double const From, double const To, double const Step, int const Last) {
|
||||
return std::make_pair(std::clamp(static_cast<int>(std::floor(From / Step)), 0, Last), std::clamp(static_cast<int>(std::ceil(To / Step)), 0, Last));
|
||||
};
|
||||
auto const [gx0, gx1]{range(tx * tilesize, (tx + 1) * tilesize, spacing, Source.cells)};
|
||||
auto const [gz0, gz1]{range(tz * tilesize, (tz + 1) * tilesize, spacing, Source.cells)};
|
||||
part.low = std::numeric_limits<float>::max();
|
||||
part.high = -std::numeric_limits<float>::max();
|
||||
for (int z = gz0; z <= gz1; ++z)
|
||||
for (int x = gx0; x <= gx1; ++x)
|
||||
{
|
||||
auto const height{Source.heights[static_cast<std::size_t>(z) * side + x]};
|
||||
part.low = std::min(part.low, height);
|
||||
part.high = std::max(part.high, height);
|
||||
}
|
||||
part.most.fill(0.f);
|
||||
auto const [sx0, sx1]{range(tx * tilesize, (tx + 1) * tilesize, step, Source.samples)};
|
||||
auto const [sz0, sz1]{range(tz * tilesize, (tz + 1) * tilesize, step, Source.samples)};
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
{
|
||||
std::uint8_t most{0};
|
||||
for (int z = sz0; z <= sz1; ++z)
|
||||
for (int x = sx0; x <= sx1; ++x)
|
||||
most = std::max(most, Source.density[kind * plane + static_cast<std::size_t>(z) * samples + x]);
|
||||
part.most[kind] = most / 255.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void grass_field::update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks)
|
||||
{
|
||||
m_kinds = Kinds;
|
||||
for (auto &entry : m_chunks)
|
||||
entry.second.listed = false;
|
||||
if (false == Global.gfx_grass)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
for (auto const &source : Chunks)
|
||||
{
|
||||
if (source.heights == nullptr || source.density == nullptr || source.cells <= 0 || source.samples <= 0)
|
||||
continue;
|
||||
auto &entry{m_chunks[source.id]};
|
||||
entry.listed = true;
|
||||
if (false == entry.uploaded || entry.version != source.version)
|
||||
upload(source.id, entry, source);
|
||||
}
|
||||
// what's no longer near goes
|
||||
for (auto it = m_chunks.begin(); it != m_chunks.end();)
|
||||
{
|
||||
if (it->second.listed)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
release(it->first, it->second);
|
||||
it = m_chunks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool grass_field::prepare(glm::dvec3 const &Camera, std::function<bool(scene::bounding_area const &)> const &Visible)
|
||||
{
|
||||
if (false == Global.gfx_grass || m_chunks.empty() || m_kinds.empty())
|
||||
return false;
|
||||
|
||||
m_chunkdraws.clear();
|
||||
m_tiledraws.clear();
|
||||
m_triangles = 0;
|
||||
// whole up to the distance, thinning out to the fading distance
|
||||
auto const fade{std::max(5.f, Global.gfx_grass_distance)};
|
||||
auto const reach{std::max(fade + 1.f, Global.gfx_grass_fadedistance)};
|
||||
auto const thinning{std::max(3.f, fade * 0.2f)};
|
||||
auto const multiplier{std::max(0.f, Global.gfx_grass_density)};
|
||||
auto const camera{Camera};
|
||||
|
||||
m_frame.lod = glm::vec4{thinning, fade, reach, 0.f};
|
||||
auto const season{grass_season(simulation::Time.year_day())};
|
||||
m_frame.season = glm::vec4{season.x, season.y, season.z, 0.f};
|
||||
// the wind sways the blades along it, harder in the gusts
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto const windspeed{glm::length(glm::vec2{wind.x, wind.z})};
|
||||
auto const winddirection{windspeed > 0.01f ? glm::vec2{wind.x, wind.z} / windspeed : glm::vec2{1.f, 0.f}};
|
||||
m_frame.wind = glm::vec4{winddirection.x, winddirection.y, std::clamp(windspeed / 15.f, 0.f, 1.f), static_cast<float>(std::fmod(Timer::GetTime(), 3600.0))};
|
||||
|
||||
for (auto &entry : m_chunks)
|
||||
{
|
||||
auto &chunk{entry.second};
|
||||
// the kinds of the chunk, and the share of the blades of each against its densest
|
||||
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
|
||||
glm::vec4 share{0.f};
|
||||
float densest{0.f};
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
if (chunk.kinds[i] >= 0 && chunk.kinds[i] < static_cast<int>(m_kinds.size()))
|
||||
densest = std::max(densest, m_kinds[chunk.kinds[i]].density);
|
||||
if (densest <= 0.f)
|
||||
continue;
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
{
|
||||
if (chunk.kinds[i] < 0 || chunk.kinds[i] >= static_cast<int>(m_kinds.size()))
|
||||
continue;
|
||||
auto const &kind{m_kinds[chunk.kinds[i]]};
|
||||
base[i] = glm::vec4{kind.base, 0.f};
|
||||
tip[i] = glm::vec4{kind.tip, 0.f};
|
||||
shape[i] = glm::vec4{kind.height, kind.variation, kind.width, kind.bend};
|
||||
dry[i] = glm::vec4{kind.dry, kind.seasonal};
|
||||
share[i] = kind.density / densest;
|
||||
}
|
||||
auto const tallest{std::max({shape[0].x, shape[1].x, shape[2].x, shape[3].x}) * 1.5f + blade_reach};
|
||||
|
||||
auto const tilesize{chunk.size / tiles};
|
||||
bool set{false};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto const &part{chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const fill{std::min(1.f, glm::dot(glm::vec4{part.most[0], part.most[1], part.most[2], part.most[3]}, share))};
|
||||
if (fill <= 0.f)
|
||||
continue;
|
||||
// distance to the box of the tile
|
||||
glm::dvec3 const low{chunk.corner.x + tx * tilesize, part.low, chunk.corner.y + tz * tilesize};
|
||||
glm::dvec3 const high{low.x + tilesize, part.high + tallest, low.z + tilesize};
|
||||
auto const nearest{glm::clamp(camera, low, high)};
|
||||
auto const distance{static_cast<float>(glm::length(nearest - camera))};
|
||||
if (distance > reach)
|
||||
continue;
|
||||
if (false == Visible(scene::bounding_area{(low + high) * 0.5, static_cast<float>(glm::length(high - low) * 0.5)}))
|
||||
continue;
|
||||
auto const full{tilesize * tilesize * densest * multiplier * fill};
|
||||
auto const count{std::min(max_blades, static_cast<int>(std::ceil(full * lod_share(distance, thinning, fade, reach))))};
|
||||
if (count <= 0)
|
||||
continue;
|
||||
if (false == set)
|
||||
{
|
||||
// the chunk is drawn relative to its corner, the heights as they are
|
||||
set = true;
|
||||
chunk_draw draw;
|
||||
draw.id = entry.first;
|
||||
draw.offset = glm::dvec3{chunk.corner.x - camera.x, -camera.y, chunk.corner.y - camera.z};
|
||||
draw.chunk = glm::vec4{chunk.size, static_cast<float>(chunk.cells), static_cast<float>(chunk.samples), 0.f};
|
||||
draw.camera = glm::vec4{static_cast<float>(camera.x - chunk.corner.x), static_cast<float>(camera.y), static_cast<float>(camera.z - chunk.corner.y), 0.f};
|
||||
draw.base = base;
|
||||
draw.tip = tip;
|
||||
draw.shape = shape;
|
||||
draw.share = share;
|
||||
draw.dry = dry;
|
||||
draw.first = m_tiledraws.size();
|
||||
m_chunkdraws.push_back(draw);
|
||||
}
|
||||
// fewer segments further away
|
||||
auto const segments{distance < thinning * 1.5f ? 3 : distance < fade * 0.5f ? 2 : 1};
|
||||
// the number of the tile in the world, the seed of its blades
|
||||
auto const seedx{static_cast<float>(std::floor(chunk.corner.x / tilesize + 0.5) + tx)};
|
||||
auto const seedz{static_cast<float>(std::floor(chunk.corner.y / tilesize + 0.5) + tz)};
|
||||
tile_draw draw;
|
||||
draw.tile = glm::vec4{tx * tilesize, tz * tilesize, tilesize, std::max(1.f, full)};
|
||||
draw.part = glm::vec4{fill, static_cast<float>(segments), seedx, seedz};
|
||||
draw.count = count;
|
||||
draw.segments = segments;
|
||||
m_tiledraws.push_back(draw);
|
||||
++m_chunkdraws.back().count;
|
||||
m_triangles += static_cast<std::size_t>(count) * (segments * 2 - 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
133
rendering/common/grassfield.h
Normal file
133
rendering/common/grassfield.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "scene/scenenode.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// grass of the heightmap terrain, whichever api draws it: blades made up by the vertex shader (grass.vert) where the grass is
|
||||
// painted, in the chunks near the camera.
|
||||
//
|
||||
// a chunk is cut into tiles; each tile visible within the reach of the grass is drawn as instances of a blade, as many as
|
||||
// its densest kind and its distance ask for. a blade finds its place, its kind and its shape from its number, so the same
|
||||
// blades stand at the same places in every frame. further away the blades thin out and grow wider, and fade out at the end
|
||||
// of the reach. the heights, the densities and the holes of a chunk are kept in textures, made by the renderer (the gpu_
|
||||
// functions) when the chunk comes near; what is drawn in a frame is worked out here (prepare())
|
||||
class grass_field
|
||||
{
|
||||
public:
|
||||
virtual ~grass_field() = default;
|
||||
|
||||
// see gfx_renderer::Terrain_Grass()
|
||||
void update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks);
|
||||
// drops everything made on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
bool empty() const
|
||||
{
|
||||
return m_chunks.empty();
|
||||
}
|
||||
|
||||
static int constexpr tiles{16}; // along a side of a chunk
|
||||
|
||||
// what a frame draws (the uniforms of grass.vert): the same for all draws
|
||||
struct frame_draw
|
||||
{
|
||||
glm::vec4 lod{0.f}; // grass_lod
|
||||
glm::vec4 season{0.f}; // grass_season
|
||||
glm::vec4 wind{0.f}; // grass_wind
|
||||
};
|
||||
// a chunk with tiles seen, drawn relative to its corner
|
||||
struct chunk_draw
|
||||
{
|
||||
std::uint64_t id{0}; // of the chunk, as the renderer got it in gpu_upload()
|
||||
glm::dvec3 offset{0.0}; // of the corner from the camera, the heights as they are
|
||||
glm::vec4 chunk{0.f}; // grass_chunk
|
||||
glm::vec4 camera{0.f}; // grass_camera
|
||||
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
|
||||
glm::vec4 share{0.f};
|
||||
std::size_t first{0}; // tiles drawn, in tile_draws()
|
||||
std::size_t count{0};
|
||||
};
|
||||
// a tile drawn: Count instances of a strip of 2 * Segments + 1 vertices
|
||||
struct tile_draw
|
||||
{
|
||||
glm::vec4 tile{0.f}; // grass_tile
|
||||
glm::vec4 part{0.f}; // grass_part
|
||||
int count{0};
|
||||
int segments{0};
|
||||
};
|
||||
// the draws of the grass seen from Camera: false when there is nothing to draw (grass off, no chunks or kinds), the draws of
|
||||
// the last call are left then. Visible: whether the bounds of a tile are seen
|
||||
bool prepare(glm::dvec3 const &Camera, std::function<bool(scene::bounding_area const &)> const &Visible);
|
||||
frame_draw const &frame() const
|
||||
{
|
||||
return m_frame;
|
||||
}
|
||||
std::vector<chunk_draw> const &chunk_draws() const
|
||||
{
|
||||
return m_chunkdraws;
|
||||
}
|
||||
std::vector<tile_draw> const &tile_draws() const
|
||||
{
|
||||
return m_tiledraws;
|
||||
}
|
||||
// the triangles of the draws prepared
|
||||
std::size_t triangles() const
|
||||
{
|
||||
return m_triangles;
|
||||
}
|
||||
|
||||
protected:
|
||||
// the textures of the chunk Id made of Source: heights (R32F, the grid points, cells + 1 along a side), densities (RGBA8, the
|
||||
// densities of the kinds side by side, samples + 1 along a side), holes (R8, a single texel of 0 without holes)
|
||||
virtual void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) = 0;
|
||||
// the textures of the chunk go; nothing when it has none
|
||||
virtual void gpu_release(std::uint64_t const Id) = 0;
|
||||
|
||||
private:
|
||||
struct tile
|
||||
{
|
||||
float low{0.f}, high{0.f}; // of the ground
|
||||
std::array<float, 4> most{}; // the highest density of each kind of the chunk within the tile, 0..1
|
||||
};
|
||||
struct chunk
|
||||
{
|
||||
std::uint64_t version{0};
|
||||
glm::dvec2 corner{0.0};
|
||||
float size{0.f};
|
||||
int cells{0};
|
||||
int samples{0};
|
||||
std::array<int, 4> kinds{-1, -1, -1, -1};
|
||||
std::array<tile, tiles * tiles> parts;
|
||||
bool uploaded{false}; // has its textures
|
||||
bool listed{false}; // in the last list given
|
||||
};
|
||||
|
||||
void upload(std::uint64_t const Id, chunk &Chunk, gfx::grass_chunk const &Source);
|
||||
void release(std::uint64_t const Id, chunk &Chunk);
|
||||
|
||||
std::vector<gfx::grass_kind> m_kinds;
|
||||
std::unordered_map<std::uint64_t, chunk> m_chunks;
|
||||
frame_draw m_frame;
|
||||
std::vector<chunk_draw> m_chunkdraws;
|
||||
std::vector<tile_draw> m_tiledraws;
|
||||
std::size_t m_triangles{0};
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
389
rendering/common/instancecull.cpp
Normal file
389
rendering/common/instancecull.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/instancecull.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/frustum.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr std::int64_t RECORD_SIZE{sizeof(scene::basic_cell::instance_record)};
|
||||
|
||||
} // namespace
|
||||
|
||||
instance_cull::instance_cull() : m_records{std::make_shared<record_state>()}
|
||||
{
|
||||
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
|
||||
}
|
||||
|
||||
void instance_cull::begin_pass(pass_setup const &Setup)
|
||||
{
|
||||
collect();
|
||||
|
||||
m_inpass = true;
|
||||
m_ran = true;
|
||||
m_dispatched = false;
|
||||
m_setup = Setup;
|
||||
m_buckets.clear();
|
||||
m_joblist.clear();
|
||||
m_grouplist.clear();
|
||||
m_drawgroups.clear();
|
||||
m_lodlist.clear();
|
||||
m_groupmap.clear();
|
||||
m_sleepergroupmap.clear();
|
||||
m_cpucounts.clear();
|
||||
m_candidates = 0;
|
||||
m_entries = 0;
|
||||
m_sleepertracks = 0;
|
||||
m_sleepers = 0;
|
||||
m_models = 0;
|
||||
m_counts = {};
|
||||
}
|
||||
|
||||
void instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, false);
|
||||
}
|
||||
|
||||
void instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
if (!m_inpass)
|
||||
return;
|
||||
++m_counts.skippedbuckets;
|
||||
m_counts.skippedinstances += Bucket.records.size();
|
||||
}
|
||||
|
||||
void instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, true);
|
||||
}
|
||||
|
||||
void instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
|
||||
{
|
||||
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
|
||||
return;
|
||||
|
||||
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
|
||||
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
|
||||
// camera of the pass
|
||||
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
|
||||
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
|
||||
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
|
||||
auto const centerdistance{glm::length(Area.center - distancecamera)};
|
||||
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
|
||||
auto const farthest{centerdistance + Area.radius};
|
||||
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
|
||||
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
|
||||
|
||||
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
{
|
||||
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
|
||||
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
|
||||
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
|
||||
|
||||
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
||||
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
||||
m_entries += group.entrycount;
|
||||
lookup = groupmap.emplace(Key, group).first;
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
// any instance of the group serves; it's only prepared and asked for its material
|
||||
TAnimModel *instance{nullptr};
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lookup->second.distancemin = std::min(lookup->second.distancemin, distancemin);
|
||||
lookup->second.distancemax = std::max(lookup->second.distancemax, distancemax);
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
auto &drawgroup{m_drawgroups[lookup->second.index]};
|
||||
drawgroup.distancemin = lookup->second.distancemin;
|
||||
drawgroup.distancemax = lookup->second.distancemax;
|
||||
if (drawgroup.instance == nullptr)
|
||||
{
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
drawgroup.instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
place(Bucket);
|
||||
|
||||
gl::instance_cull_job job{};
|
||||
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
|
||||
job.first = Bucket.gpufirst;
|
||||
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
|
||||
job.count = static_cast<std::uint32_t>(Bucket.records.size());
|
||||
job.group = lookup->second.index;
|
||||
job.candidatestart = m_candidates;
|
||||
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
|
||||
m_joblist.push_back(job);
|
||||
m_buckets.push_back(&Bucket);
|
||||
m_candidates += job.count;
|
||||
if (Sleepers)
|
||||
{
|
||||
++m_sleepertracks;
|
||||
m_sleepers += job.count;
|
||||
}
|
||||
else if (Key.instance != nullptr)
|
||||
{
|
||||
++m_models;
|
||||
}
|
||||
}
|
||||
|
||||
void instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
auto &shared{*m_records};
|
||||
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
|
||||
auto const current{Bucket.gpugeneration == shared.recordgeneration};
|
||||
if (current && !Bucket.gpustale)
|
||||
return;
|
||||
if (current && count <= Bucket.gpucapacity)
|
||||
{
|
||||
send_records(Bucket.gpufirst, Bucket.records.data(), count);
|
||||
m_counts.sentrecords += count;
|
||||
Bucket.gpustale = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// a new place, with some room for instances the editor adds
|
||||
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
|
||||
auto const records{static_cast<std::uint32_t>(Target.records.size())};
|
||||
Target.gpufirst = shared.recordfill;
|
||||
Target.gpucapacity = records + records / 4;
|
||||
Target.gpugeneration = shared.recordgeneration;
|
||||
Target.gpustale = false;
|
||||
shared.recordfill += Target.gpucapacity;
|
||||
send_records(Target.gpufirst, Target.records.data(), records);
|
||||
m_counts.sentrecords += records;
|
||||
};
|
||||
|
||||
auto const capacity{static_cast<std::uint32_t>(record_buffer_size() / RECORD_SIZE)};
|
||||
if (shared.recordfill + count + count / 4 > capacity)
|
||||
{
|
||||
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
|
||||
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
|
||||
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
|
||||
// so a buffer started over again in the same or the next frame grows
|
||||
std::int64_t live{count + count / 4};
|
||||
for (auto const *bucket : m_buckets)
|
||||
live += bucket->records.size() + bucket->records.size() / 4;
|
||||
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
|
||||
if ((2 * live * RECORD_SIZE > record_buffer_size()) || again)
|
||||
resize_record_buffer(std::max(2 * live * RECORD_SIZE, 2 * record_buffer_size()));
|
||||
else
|
||||
restart_records();
|
||||
shared.restarted = true;
|
||||
shared.restartframe = m_setup.frame;
|
||||
m_counts.restarted = true;
|
||||
++shared.recordgeneration;
|
||||
shared.recordfill = 0;
|
||||
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||
{
|
||||
allocate(*m_buckets[job]);
|
||||
m_joblist[job].first = m_buckets[job]->gpufirst;
|
||||
}
|
||||
}
|
||||
allocate(Bucket);
|
||||
}
|
||||
|
||||
bool instance_cull::dispatch_begin()
|
||||
{
|
||||
m_occlusion = false;
|
||||
m_twophase = false;
|
||||
return (m_inpass && !m_joblist.empty());
|
||||
}
|
||||
|
||||
void instance_cull::frustum_planes(glm::vec4 (&Planes)[6]) const
|
||||
{
|
||||
auto const &frustumplanes{m_setup.frustum->planes()};
|
||||
for (int plane = 0; plane < 6; ++plane)
|
||||
{
|
||||
auto const *source{frustumplanes[plane]};
|
||||
Planes[plane] = glm::vec4{source[0], source[1], source[2],
|
||||
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
|
||||
}
|
||||
}
|
||||
|
||||
void instance_cull::dispatch_end()
|
||||
{
|
||||
m_cpucounts.assign(m_entries, 0);
|
||||
m_dispatched = true;
|
||||
}
|
||||
|
||||
void instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
|
||||
{
|
||||
m_draws = Draws;
|
||||
m_prepassdraws = Prepassdraws;
|
||||
m_calls = Calls;
|
||||
m_prepasscalls = Prepasscalls;
|
||||
m_cputime = Cputime;
|
||||
}
|
||||
|
||||
void instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto const lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
|
||||
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
|
||||
}
|
||||
|
||||
void instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const lookup{m_groupmap.find(Key)};
|
||||
if (lookup == m_groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
auto const &lods{m_grouplist[group.index]};
|
||||
// the band the culling gives the distance: the number of bounds it reached
|
||||
auto const first{m_lodlist.begin() + lods.lodfirst};
|
||||
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
|
||||
if (band < group.entrycount)
|
||||
++m_cpucounts[group.entryfirst + band];
|
||||
}
|
||||
|
||||
void instance_cull::end_pass()
|
||||
{
|
||||
// the counts of both phases, for the panel and the comparison
|
||||
if (m_dispatched)
|
||||
push_counts();
|
||||
// the cpu counts are there to compare with only when the cpu drew
|
||||
if (m_dispatched && !m_setup.drawing)
|
||||
{
|
||||
std::uint64_t total{0};
|
||||
for (auto const count : m_cpucounts)
|
||||
total += count;
|
||||
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
|
||||
// the gpu is never this far behind
|
||||
while (m_cpupasses.size() > 8)
|
||||
m_cpupasses.pop_front();
|
||||
}
|
||||
m_lastsleepertracks = m_sleepertracks;
|
||||
m_lastsleepers = m_sleepers;
|
||||
m_lastmodels = m_models;
|
||||
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
|
||||
m_counts.candidates = m_candidates;
|
||||
m_lastcounts = m_counts;
|
||||
m_inpass = false;
|
||||
m_dispatched = false;
|
||||
}
|
||||
|
||||
void instance_cull::collect()
|
||||
{
|
||||
if (!pop_counts(m_readbackdata.data()))
|
||||
return;
|
||||
|
||||
auto const frame{m_readbackdata[0]};
|
||||
comparison result;
|
||||
result.frame = frame;
|
||||
result.occluded = m_readbackdata[3];
|
||||
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
|
||||
result.entries = m_readbackdata[2];
|
||||
result.first = m_readbackdata[4];
|
||||
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||
m_cpupasses.pop_front();
|
||||
if (m_cpupasses.empty())
|
||||
{
|
||||
// drawn from the gpu: nothing to compare with
|
||||
m_last = result;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const &cpu{m_cpupasses.front()};
|
||||
result.cputotal = cpu.total;
|
||||
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
|
||||
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
|
||||
{
|
||||
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
|
||||
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
|
||||
if (difference > 0)
|
||||
{
|
||||
++result.mismatched;
|
||||
result.largestdifference = std::max(result.largestdifference, difference);
|
||||
}
|
||||
}
|
||||
m_cpupasses.pop_front();
|
||||
|
||||
m_last = result;
|
||||
++m_comparedframes;
|
||||
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
|
||||
++m_mismatchedframes;
|
||||
}
|
||||
|
||||
std::string instance_cull::info(std::string const &Label, bool const Occlusion)
|
||||
{
|
||||
if (Global.gfx_gpucull == 0)
|
||||
return {};
|
||||
// the sleepers are among the instances counted
|
||||
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
|
||||
// and so are the models drawn on their own
|
||||
if (m_lastmodels > 0)
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
|
||||
// what the pass tested and sent to the gpu
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
|
||||
if (Global.gfx_gpucull_bucketrange)
|
||||
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling";
|
||||
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
if (m_last.frame == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
if (false == Occlusion)
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
|
||||
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
|
||||
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
|
||||
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
|
||||
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
|
||||
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
}
|
||||
if (m_comparedframes == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
|
||||
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
|
||||
std::to_string(m_comparedframes) + sleepers;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
303
rendering/common/instancecull.h
Normal file
303
rendering/common/instancecull.h
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
class cFrustum;
|
||||
class TAnimModel;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// the cpu side of the culling of the model instances of a render pass in compute passes, shared by the renderers (stage N6c of
|
||||
// claude/vulkan14-renderer-plan.md); the backend (opengl46_instance_cull, vk14 instance_cull) keeps the buffers and runs the passes:
|
||||
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
|
||||
// in its own place, and sent again only when the bucket made them anew
|
||||
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
|
||||
// lod bands are its entries
|
||||
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
|
||||
// modelview matrices of the instances there
|
||||
// with Global.gfx_gpucull 1 the instances are drawn from the result: the renderer turns each lod band of a group into a
|
||||
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
|
||||
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer. an occlusion test
|
||||
// against a depth pyramid of what the pass drew before can leave out more instances. in two phases the instances seen in the
|
||||
// previous frame are culled and drawn first, then the pyramid is made again with them in it and the others are tested
|
||||
// against it, so the instances hide one another too. the command buffer holds the commands three times over: the instances
|
||||
// drawn first, all of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
|
||||
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
|
||||
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
|
||||
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map, the
|
||||
// shadow maps of headlights and the scenery parts of those of lamps) has a culling of its own, with its own buffers, while the
|
||||
// records and the programs are shared by all of them: the records of a bucket go to the gpu once and serve every pass which
|
||||
// queues the bucket
|
||||
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
|
||||
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
|
||||
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
|
||||
// its key), whose submodel tree the renderer animates and walks for it alone
|
||||
class instance_cull
|
||||
{
|
||||
public:
|
||||
// what a pass needs for the tests, the way Render_Instanced() takes it
|
||||
struct pass_setup
|
||||
{
|
||||
unsigned int frame{0};
|
||||
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
|
||||
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
|
||||
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
|
||||
float distancescale{1.f}; // of the squared distance
|
||||
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
|
||||
float distancerange{0.f}; // squared range of the scaled distance before the offset
|
||||
float drawrange{0.f}; // squared draw range
|
||||
float zoom{1.f};
|
||||
bool drawing{false}; // the instances are drawn from the result (Global.gfx_gpucull 1), not compared
|
||||
};
|
||||
// a group of instances (model and skins) of the pass, for the renderer to make the draw commands of
|
||||
struct draw_group
|
||||
{
|
||||
scene::basic_cell::instance_bucket_key const *key;
|
||||
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
|
||||
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
|
||||
std::uint32_t lodfirst; // in lod_bounds()
|
||||
std::uint32_t lodcount;
|
||||
std::uint32_t entryfirst;
|
||||
float distancemin; // squared distances (scaled as the culling does) the instances of the group can be at
|
||||
float distancemax;
|
||||
};
|
||||
// copies of the commands in the command buffer
|
||||
enum class command_set : std::uint32_t
|
||||
{
|
||||
first = 0, // instances which passed the first dispatch: in one phase all of them
|
||||
all = 1, // after the second phase
|
||||
second = 2 // the ones which passed the second phase
|
||||
};
|
||||
|
||||
instance_cull();
|
||||
instance_cull(const instance_cull &) = delete;
|
||||
instance_cull &operator=(const instance_cull &) = delete;
|
||||
virtual ~instance_cull() = default;
|
||||
|
||||
// starts collecting the buckets of a pass
|
||||
void begin_pass(pass_setup const &Setup);
|
||||
bool in_pass() const
|
||||
{
|
||||
return m_inpass;
|
||||
}
|
||||
bool drawing() const
|
||||
{
|
||||
return m_inpass && m_setup.drawing;
|
||||
}
|
||||
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed.
|
||||
// Area: bounds which hold the locations of all its visible instances: their own (scene_walk::Bucket_Area()) or of the cell
|
||||
// holding the bucket
|
||||
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place
|
||||
// in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by
|
||||
// the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the
|
||||
// camera of the pass (Render_Sleepers())
|
||||
void add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted,
|
||||
// for the panel
|
||||
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// the last dispatch works in two phases
|
||||
bool two_phase() const
|
||||
{
|
||||
return m_twophase;
|
||||
}
|
||||
// groups of the pass and the lod bounds they refer to, valid from the dispatch to end_pass()
|
||||
std::vector<draw_group> const &draw_groups() const
|
||||
{
|
||||
return m_drawgroups;
|
||||
}
|
||||
std::vector<float> const &lod_bounds() const
|
||||
{
|
||||
return m_lodlist;
|
||||
}
|
||||
// offset of command Index of a copy in the command buffer
|
||||
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
|
||||
{
|
||||
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
|
||||
}
|
||||
// counts of the last result which came back from the gpu, with the draws of the pass and the calls which made them (fewer
|
||||
// with multi draws), for the debug panel
|
||||
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
|
||||
// what draw_result() was given last
|
||||
std::uint32_t prepass_draws() const
|
||||
{
|
||||
return m_prepassdraws;
|
||||
}
|
||||
std::uint32_t prepass_calls() const
|
||||
{
|
||||
return m_prepasscalls;
|
||||
}
|
||||
double cpu_time() const
|
||||
{
|
||||
return m_cputime;
|
||||
}
|
||||
// instances which passed in the last result which came back from the gpu, a few frames old
|
||||
std::uint32_t last_count() const
|
||||
{
|
||||
return static_cast<std::uint32_t>(m_last.gputotal);
|
||||
}
|
||||
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
|
||||
// Render_Sleepers() drew. needs the dispatch before it
|
||||
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
|
||||
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
|
||||
// scales it: counted in the lod band the distance falls in. needs the dispatch before it
|
||||
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
|
||||
// ends the pass: its cpu counts are kept for the comparison
|
||||
void end_pass();
|
||||
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
|
||||
// for the panel also by the culls of passes which aren't made every frame
|
||||
void collect();
|
||||
// a pass was culled since the start
|
||||
bool ran() const
|
||||
{
|
||||
return m_ran;
|
||||
}
|
||||
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
|
||||
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
|
||||
|
||||
protected:
|
||||
// what the cullings of all passes share of the record buffer: records placed so far, the generation which tells current places
|
||||
// from the ones of a replaced buffer, the frame of the last time the buffer was started over, and whether it was
|
||||
struct record_state
|
||||
{
|
||||
std::uint32_t recordfill{0};
|
||||
unsigned int recordgeneration{1};
|
||||
unsigned int restartframe{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
// entries read back for the comparison; with more of them only the totals and these are compared
|
||||
static constexpr std::uint32_t READBACK_ENTRIES{16384};
|
||||
|
||||
// what the backend does with the buffers
|
||||
// size of the record buffer, bytes, and a new one of the given size (the contents are dropped)
|
||||
virtual std::int64_t record_buffer_size() const = 0;
|
||||
virtual void resize_record_buffer(std::int64_t const Size) = 0;
|
||||
// sends records to their place in the record buffer
|
||||
virtual void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) = 0;
|
||||
// the buffer is started over without growing: the records go to the places of the buffer from its start again
|
||||
virtual void restart_records() {}
|
||||
// the counters of the pass (INSTANCE_COUNTER_HEADER + READBACK_ENTRIES words) go back to the cpu, and the oldest of them which
|
||||
// arrived: false when none did
|
||||
virtual void push_counts() = 0;
|
||||
virtual bool pop_counts(std::uint32_t *Counts) = 0;
|
||||
|
||||
// the start of the dispatch of the first phase: false when there's nothing to cull
|
||||
bool dispatch_begin();
|
||||
// planes of the frustum of the pass moved to its camera, in double precision: the cpu tests world coordinates
|
||||
void frustum_planes(glm::vec4 (&Planes)[6]) const;
|
||||
// the end of the dispatch of the first phase
|
||||
void dispatch_end();
|
||||
// with a culling made for another pass: shares the records with it
|
||||
void share_records(instance_cull const &Shared)
|
||||
{
|
||||
m_records = Shared.m_records;
|
||||
}
|
||||
|
||||
struct group_data
|
||||
{
|
||||
std::uint32_t index;
|
||||
std::uint32_t entryfirst;
|
||||
std::uint32_t entrycount;
|
||||
float distancemin;
|
||||
float distancemax;
|
||||
};
|
||||
// counts of the cpu of a pass, waiting for those of the gpu
|
||||
struct cpu_pass
|
||||
{
|
||||
unsigned int frame;
|
||||
std::vector<std::uint32_t> counts;
|
||||
std::uint64_t total;
|
||||
};
|
||||
struct comparison
|
||||
{
|
||||
unsigned int frame{0};
|
||||
std::uint64_t gputotal{0};
|
||||
std::uint64_t cputotal{0};
|
||||
std::uint32_t entries{0};
|
||||
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
|
||||
std::uint32_t mismatched{0};
|
||||
std::uint32_t largestdifference{0};
|
||||
std::uint32_t occluded{0}; // left out by the occlusion test
|
||||
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
|
||||
};
|
||||
|
||||
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
|
||||
|
||||
// add_bucket() and add_sleepers()
|
||||
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
|
||||
// finds a place for the records of a bucket in the record buffer and sends them
|
||||
void place(scene::basic_cell::instance_bucket const &Bucket);
|
||||
|
||||
std::shared_ptr<record_state> m_records;
|
||||
|
||||
bool m_inpass{false};
|
||||
bool m_ran{false};
|
||||
pass_setup m_setup;
|
||||
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
|
||||
std::vector<gl::instance_cull_job> m_joblist;
|
||||
std::vector<gl::instance_cull_group> m_grouplist;
|
||||
std::vector<draw_group> m_drawgroups;
|
||||
std::vector<float> m_lodlist;
|
||||
group_map m_groupmap;
|
||||
group_map m_sleepergroupmap; // groups of the sleepers
|
||||
std::uint32_t m_candidates{0};
|
||||
std::uint32_t m_entries{0};
|
||||
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_sleepertracks{0};
|
||||
std::uint32_t m_sleepers{0};
|
||||
std::uint32_t m_lastsleepertracks{0};
|
||||
std::uint32_t m_lastsleepers{0};
|
||||
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_models{0};
|
||||
std::uint32_t m_lastmodels{0};
|
||||
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
|
||||
// their range and their instances, records sent to the gpu, the record buffer started over
|
||||
struct pass_counts
|
||||
{
|
||||
std::uint32_t jobs{0};
|
||||
std::uint32_t candidates{0};
|
||||
std::uint32_t skippedbuckets{0};
|
||||
std::uint64_t skippedinstances{0};
|
||||
std::uint64_t sentrecords{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
pass_counts m_counts;
|
||||
pass_counts m_lastcounts;
|
||||
bool m_dispatched{false};
|
||||
|
||||
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
|
||||
std::deque<cpu_pass> m_cpupasses;
|
||||
std::vector<std::uint32_t> m_readbackdata;
|
||||
comparison m_last;
|
||||
std::uint64_t m_comparedframes{0};
|
||||
std::uint64_t m_mismatchedframes{0};
|
||||
// draws of the last pass drawn from the gpu
|
||||
std::uint32_t m_draws{0};
|
||||
std::uint32_t m_prepassdraws{0};
|
||||
std::uint32_t m_calls{0};
|
||||
std::uint32_t m_prepasscalls{0};
|
||||
std::uint32_t m_commandcount{0};
|
||||
double m_cputime{0.0};
|
||||
bool m_occlusion{false}; // the last dispatch tested occlusion
|
||||
bool m_twophase{false}; // in two phases
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
648
rendering/common/localshadows.cpp
Normal file
648
rendering/common/localshadows.cpp
Normal file
@@ -0,0 +1,648 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/localshadows.h"
|
||||
|
||||
#include "rendering/openglcamera.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "vehicle/Train.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres
|
||||
double const LAMP_MOVED{0.01};
|
||||
// lamps with a shorter reach aren't worth a map, metres
|
||||
float const LAMP_RANGE_MIN{1.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
local_shadows::level_setup local_shadows::setup(int const Level)
|
||||
{
|
||||
switch (Level)
|
||||
{
|
||||
case 1:
|
||||
return {512, 4, 0.f, 1, 1024, 2, 8};
|
||||
case 2:
|
||||
return {1024, 2, 1.f, 1, 1024, 1, 4};
|
||||
case 3:
|
||||
return {1024, 1, 2.f, 2, 2048, 1, 2};
|
||||
default:
|
||||
return {0, 0, 0.f, 0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void local_shadows::allocate(int const Size)
|
||||
{
|
||||
release();
|
||||
if (Size <= 0)
|
||||
return;
|
||||
if (false == gpu_allocate(Size))
|
||||
{
|
||||
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
|
||||
release();
|
||||
Global.gfx_lights_shadows = 0;
|
||||
return;
|
||||
}
|
||||
m_size = Size;
|
||||
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
|
||||
}
|
||||
|
||||
void local_shadows::release()
|
||||
{
|
||||
gpu_release();
|
||||
m_size = 0;
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
void local_shadows::allocate_lamps(int const Count, int const Size)
|
||||
{
|
||||
release_lamps();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
// the scenery parts and the working copies
|
||||
if (false == gpu_allocate_lamps(Count, Size))
|
||||
{
|
||||
ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows");
|
||||
release_lamps();
|
||||
Global.gfx_lights_shadows_lamps = 0;
|
||||
return;
|
||||
}
|
||||
m_lampssize = Size;
|
||||
m_slots.assign(static_cast<std::size_t>(Count), lamp_slot{});
|
||||
WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
|
||||
std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
}
|
||||
|
||||
void local_shadows::release_lamps()
|
||||
{
|
||||
gpu_release_lamps();
|
||||
m_lampssize = 0;
|
||||
m_slots.clear();
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
void local_shadows::allocate_headlights(int const Count, int const Size)
|
||||
{
|
||||
release_headlights();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
if (false == gpu_allocate_headlights(Count, Size))
|
||||
{
|
||||
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
|
||||
release_headlights();
|
||||
Global.gfx_lights_shadows_headlights = 0;
|
||||
return;
|
||||
}
|
||||
m_headlightssize = Size;
|
||||
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
|
||||
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
|
||||
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
}
|
||||
|
||||
void local_shadows::release_headlights()
|
||||
{
|
||||
gpu_release_headlights();
|
||||
m_headlightssize = 0;
|
||||
m_headlightslots.clear();
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
bool local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
|
||||
{
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
if (level != m_level)
|
||||
{
|
||||
m_level = level;
|
||||
auto const size{setup(level).size};
|
||||
if (size != m_size)
|
||||
allocate(size);
|
||||
m_valid = false;
|
||||
}
|
||||
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
|
||||
{
|
||||
// with the lamp off nothing reads the map; once it is on again the cab may have changed
|
||||
m_valid = false;
|
||||
return false;
|
||||
}
|
||||
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
// the moving parts of the cab are followed every few frames
|
||||
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_vehicle = Vehicle;
|
||||
m_cab = Cab;
|
||||
m_lamp = Lamp;
|
||||
m_drawnframe = Framestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
void local_shadows::begin()
|
||||
{
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
gpu_begin_cab();
|
||||
}
|
||||
|
||||
void local_shadows::end(std::uint32_t const Drawcalls)
|
||||
{
|
||||
gpu_end_cab();
|
||||
m_valid = true;
|
||||
m_drawcalls = Drawcalls;
|
||||
++m_updatecount;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
glm::vec4 local_shadows::weights() const
|
||||
{
|
||||
// a face spans twice the distance from the lamp
|
||||
return glm::vec4{static_cast<float>(cab_slot), shadow_near, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
m_frame = Framestamp;
|
||||
m_wanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0};
|
||||
auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_slots.size()) || (size != m_lampssize))
|
||||
{
|
||||
allocate_lamps(count, size);
|
||||
}
|
||||
if (m_slots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_sceneryleft = setup(level).sceneryperframe;
|
||||
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
|
||||
// the lights come the most important first: the first ones within reach get the slots
|
||||
std::vector<gfx::local_lights::light const *> picked;
|
||||
for (auto const &light : Lights)
|
||||
{
|
||||
if (picked.size() >= m_slots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
picked.emplace_back(&light);
|
||||
}
|
||||
// lamps which already have a slot keep it
|
||||
std::vector<gfx::local_lights::light const *> unplaced;
|
||||
for (auto const *light : picked)
|
||||
{
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
if (glm::length(slot.position - light->position) > LAMP_MOVED)
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of lamps no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = lamp_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.part = light->part;
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// the scene changed (events, the editor): the scenery parts are out of date
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
if (slot.scenery && (slot.version != Version))
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void local_shadows::reset_lamps()
|
||||
{
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
slot = lamp_slot{};
|
||||
}
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
bool local_shadows::scenery_wanted(std::size_t const Slot)
|
||||
{
|
||||
if (m_slots[Slot].scenery || (m_sceneryleft <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--m_sceneryleft;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_slots[Slot]};
|
||||
return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval));
|
||||
}
|
||||
|
||||
void local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_slots[Slot]};
|
||||
auto const scenerycube{static_cast<int>(Slot)};
|
||||
auto const workingcube{static_cast<int>(m_slots.size() + Slot)};
|
||||
if (Vehicles)
|
||||
{
|
||||
// the vehicles go over the scenery
|
||||
slot.vehicles = true;
|
||||
slot.vehiclesframe = Framestamp;
|
||||
++m_lampsvehicles;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.scenery = true;
|
||||
slot.version = Version;
|
||||
slot.vehicles = false;
|
||||
++m_lampsscenery;
|
||||
}
|
||||
gpu_begin_lamp(scenerycube, workingcube, Vehicles);
|
||||
}
|
||||
|
||||
void local_shadows::end_lamp(std::uint32_t const Drawcalls)
|
||||
{
|
||||
gpu_end_lamp();
|
||||
m_lampsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void local_shadows::lamps_timer_begin()
|
||||
{
|
||||
m_lampsframedrawcalls = 0;
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
gpu_lamps_timer(true);
|
||||
m_lampstiming = true;
|
||||
}
|
||||
|
||||
void local_shadows::lamps_timer_end()
|
||||
{
|
||||
if (false == m_lampstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gpu_lamps_timer(false);
|
||||
m_lampstiming = false;
|
||||
m_lampsdrawcalls = m_lampsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
local_shadows::lamp_slot const *local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
|
||||
{
|
||||
if (Light.owner == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
auto const &slot{m_slots[idx]};
|
||||
if ((slot.owner == Light.owner) && (slot.part == Light.part))
|
||||
{
|
||||
if (false == slot.scenery)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
Slot = idx;
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glm::vec4 local_shadows::lamp_weights(std::size_t const Slot) const
|
||||
{
|
||||
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
|
||||
return glm::vec4{static_cast<float>(cube), shadow_near, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
std::vector<TDynamicObject *> const &local_shadows::lamp_vehicles(glm::dvec3 const &Camera)
|
||||
{
|
||||
m_lampvehicles.clear();
|
||||
auto const vehiclesrange{static_cast<double>(Global.gfx_lights_shadows_vehicles_range)};
|
||||
if (vehiclesrange > 0.0)
|
||||
{
|
||||
for (auto *vehicle : simulation::Vehicles.sequence())
|
||||
{
|
||||
if (glm::length(vehicle->GetPosition() - Camera) <= vehiclesrange + vehicle->radius())
|
||||
m_lampvehicles.emplace_back(vehicle);
|
||||
}
|
||||
}
|
||||
return m_lampvehicles;
|
||||
}
|
||||
|
||||
std::vector<TDynamicObject *> const &local_shadows::slot_vehicles(lamp_slot const &Slot)
|
||||
{
|
||||
m_slotvehicles.clear();
|
||||
for (auto *vehicle : m_lampvehicles)
|
||||
{
|
||||
if (glm::length(vehicle->GetPosition() - Slot.position) <= Slot.range + vehicle->radius())
|
||||
m_slotvehicles.emplace_back(vehicle);
|
||||
}
|
||||
return m_slotvehicles;
|
||||
}
|
||||
|
||||
std::vector<light_array::light_record const *> const &local_shadows::pick_headlights(light_array const &Lights, opengl_camera const &Camera)
|
||||
{
|
||||
m_headlightpicked.clear();
|
||||
auto const count{(Global.gfx_lights_headlights && (std::clamp(Global.gfx_lights_shadows, 0, 3) > 0)) ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
if (count > 0)
|
||||
{
|
||||
m_headlightcandidates.clear();
|
||||
auto const camera{Camera.position()};
|
||||
auto const range{static_cast<double>(Global.gfx_lights_shadows_headlights_range)};
|
||||
for (auto const &light : Lights.data)
|
||||
{
|
||||
if (light.intensity == 0.f)
|
||||
continue;
|
||||
// as in the light buffers: by day the light around takes the beam down to nothing
|
||||
auto luminance{static_cast<float>(Global.fLuminance)};
|
||||
if (light.owner->fShade > 0.f)
|
||||
luminance *= light.owner->fShade;
|
||||
auto const beam{light.color - glm::vec3{luminance}};
|
||||
if (std::max(beam.r, std::max(beam.g, beam.b)) <= 0.f)
|
||||
continue;
|
||||
// the part of the beam the shadows are cast in: from the end of the vehicle to the reach ahead of it
|
||||
auto const &dimensions{light.owner->MoverParameters->Dim};
|
||||
auto const radius{range * 0.5 + std::max(dimensions.W, dimensions.H) + 2.0};
|
||||
auto const centre{light.position + glm::dvec3{light.direction} * (range * 0.5 + 2.0)};
|
||||
if (glm::length(centre - camera) - radius > range)
|
||||
continue;
|
||||
if (false == Camera.visible(scene::bounding_area{centre, static_cast<float>(radius)}))
|
||||
continue;
|
||||
m_headlightcandidates.emplace_back(static_cast<float>(glm::length2(camera - light.position)) / light.intensity, &light);
|
||||
}
|
||||
auto const picked{std::min(m_headlightcandidates.size(), static_cast<std::size_t>(count))};
|
||||
std::partial_sort(std::begin(m_headlightcandidates), std::begin(m_headlightcandidates) + picked, std::end(m_headlightcandidates),
|
||||
[](auto const &Left, auto const &Right) { return Left.first < Right.first; });
|
||||
for (std::size_t idx = 0; idx < picked; ++idx)
|
||||
{
|
||||
m_headlightpicked.emplace_back(m_headlightcandidates[idx].second);
|
||||
}
|
||||
}
|
||||
return m_headlightpicked;
|
||||
}
|
||||
|
||||
void local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
|
||||
{
|
||||
m_headlightswanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
auto const size{count > 0 ? setup(level).headlightsize : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
|
||||
{
|
||||
allocate_headlights(count, size);
|
||||
}
|
||||
if (m_headlightslots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// sets which already have a slot keep it
|
||||
std::vector<light_array::light_record const *> unplaced;
|
||||
for (auto const *light : Lights)
|
||||
{
|
||||
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of sets no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = headlight_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.index = light->index;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void local_shadows::reset_headlights()
|
||||
{
|
||||
for (auto &slot : m_headlightslots)
|
||||
{
|
||||
slot = headlight_slot{};
|
||||
}
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
bool local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_headlightslots[Slot]};
|
||||
auto const level{setup(m_level)};
|
||||
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
|
||||
}
|
||||
|
||||
void local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_headlightslots[Slot]};
|
||||
slot.drawn = true;
|
||||
slot.drawnframe = Framestamp;
|
||||
slot.projector = Projector;
|
||||
slot.rotation = Rotation;
|
||||
slot.projection = Projection;
|
||||
slot.direction = Direction;
|
||||
++m_headlightsdrawn;
|
||||
gpu_begin_headlight(Slot);
|
||||
}
|
||||
|
||||
void local_shadows::end_headlight(std::uint32_t const Drawcalls)
|
||||
{
|
||||
gpu_end_headlight();
|
||||
m_headlightsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void local_shadows::headlights_timer_begin()
|
||||
{
|
||||
m_headlightsframedrawcalls = 0;
|
||||
m_headlightscpustart = std::chrono::steady_clock::now();
|
||||
gpu_headlights_timer(true);
|
||||
m_headlightstiming = true;
|
||||
}
|
||||
|
||||
void local_shadows::headlights_timer_end()
|
||||
{
|
||||
if (false == m_headlightstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gpu_headlights_timer(false);
|
||||
m_headlightstiming = false;
|
||||
m_headlightsdrawcalls = m_headlightsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
|
||||
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
int local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
|
||||
{
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
auto const &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == Owner) && (slot.index == Index))
|
||||
{
|
||||
return (slot.drawn ? static_cast<int>(idx) : -1);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
glm::vec4 local_shadows::headlight_params(std::size_t const Slot) const
|
||||
{
|
||||
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
|
||||
auto const &projection{m_headlightslots[Slot].projection};
|
||||
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
|
||||
}
|
||||
|
||||
TDynamicObject *local_shadows::shadow_cab()
|
||||
{
|
||||
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
|
||||
return nullptr;
|
||||
auto *cab{simulation::Train->Dynamic()};
|
||||
if ((cab != nullptr) && ((false == cab->bDisplayCab) || (cab->mdKabina == nullptr) || (cab->mdKabina == cab->mdModel)))
|
||||
cab = nullptr;
|
||||
return cab;
|
||||
}
|
||||
|
||||
std::string local_shadows::info() const
|
||||
{
|
||||
std::string text;
|
||||
if (m_size != 0)
|
||||
{
|
||||
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
|
||||
" ms";
|
||||
}
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto withvehicles{0};
|
||||
auto ready{0};
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
ready += (m_slots[idx].scenery ? 1 : 0);
|
||||
withvehicles += (m_slots[idx].vehicles ? 1 : 0);
|
||||
}
|
||||
text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " +
|
||||
std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " +
|
||||
std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms";
|
||||
}
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto ready{0};
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
ready += (m_headlightslots[idx].drawn ? 1 : 0);
|
||||
}
|
||||
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
|
||||
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
|
||||
to_string(m_headlightscputime, 2) + " ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void local_shadows::draw_debug_ui()
|
||||
{
|
||||
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
|
||||
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
|
||||
if (m_size != 0)
|
||||
{
|
||||
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
|
||||
}
|
||||
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
|
||||
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);
|
||||
ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f);
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.find("\nscenery lamp shadows:")};
|
||||
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
|
||||
if (line != std::string::npos)
|
||||
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
|
||||
}
|
||||
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
|
||||
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.rfind('\n')};
|
||||
ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
288
rendering/common/localshadows.h
Normal file
288
rendering/common/localshadows.h
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rendering/common/locallights.h"
|
||||
#include "rendering/lightarray.h"
|
||||
|
||||
class TDynamicObject;
|
||||
class TModel3d;
|
||||
class opengl_camera;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// the shadow maps of the lamps (Global.gfx_lights_shadows), the part shared by the renderers (stage P4 of
|
||||
// claude/vulkan14-renderer-plan.md): which lamps and headlights get maps, when they are drawn, what the shading reads; the
|
||||
// backend (opengl46_local_shadows, the vulkan renderer) keeps the maps and draws to them through the gpu_* methods.
|
||||
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
|
||||
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
|
||||
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
|
||||
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
|
||||
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
|
||||
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
|
||||
// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there
|
||||
// are any by the lamp. the shading reads the working copy then, the scenery part otherwise.
|
||||
// the six faces of a map are drawn at once, so a model costs one draw call per submodel, as in the colour pass. the depth is
|
||||
// n / d (d the distance along the axis of the face), see local_light_shadow() in light_common.glsl, which reads the map for the
|
||||
// light with the layer in its headlight_weights.x, from the array of scenery lamps when its linear is 1 and from the one of the
|
||||
// cab lamp otherwise.
|
||||
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
|
||||
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle), with what is
|
||||
// within Global.gfx_lights_shadows_headlights_range ahead of the vehicle, but not the vehicle itself. the map keeps the projector
|
||||
// it was drawn with, in world space: one a few frames old is still right for what doesn't move. read by headlight_beam_shadow()
|
||||
// in light_common.glsl, for the headlights with the map in their headlight_weights.w (1 + the layer) and the projection to it in
|
||||
// gl::scene_ubs::headlight_shadow
|
||||
class local_shadows
|
||||
{
|
||||
public:
|
||||
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
|
||||
static constexpr float shadow_near{0.15f};
|
||||
// layer of the map of the cab lamp in its array
|
||||
static constexpr int cab_slot{0};
|
||||
|
||||
local_shadows() = default;
|
||||
virtual ~local_shadows() = default;
|
||||
local_shadows(const local_shadows &) = delete;
|
||||
local_shadows &operator=(const local_shadows &) = delete;
|
||||
|
||||
// the cab lamp
|
||||
|
||||
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
|
||||
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
|
||||
// the size of the level when the level changed
|
||||
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
|
||||
// sets up the drawing to the map: the target, viewport, cleared faces, the state of the faces. the geometry is to be
|
||||
// drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map
|
||||
void begin();
|
||||
// ends the drawing to the map
|
||||
void end(std::uint32_t const Drawcalls);
|
||||
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
|
||||
bool ready(TDynamicObject const *Vehicle) const
|
||||
{
|
||||
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
|
||||
}
|
||||
// position of the lamp the map was drawn for, in the space of the cab model
|
||||
glm::vec3 const &lamp() const
|
||||
{
|
||||
return m_lamp;
|
||||
}
|
||||
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
|
||||
// radius of the filter in texels
|
||||
glm::vec4 weights() const;
|
||||
|
||||
// lamps of scenery models
|
||||
|
||||
// a lamp with a slot
|
||||
struct lamp_slot
|
||||
{
|
||||
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
|
||||
void const *part{nullptr};
|
||||
glm::dvec3 position{0.0}; // world space
|
||||
float range{0.f}; // of the light, metres
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
|
||||
bool scenery{false}; // the scenery part of the map is drawn
|
||||
std::uint64_t version{0}; // of the scene the scenery part was drawn for
|
||||
bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads
|
||||
unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in
|
||||
};
|
||||
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
|
||||
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
|
||||
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
|
||||
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
|
||||
void reset_lamps();
|
||||
// slots of the lamps picked in this frame
|
||||
std::vector<std::size_t> const &lamps() const
|
||||
{
|
||||
return m_wanted;
|
||||
}
|
||||
lamp_slot const &slot(std::size_t const Slot) const
|
||||
{
|
||||
return m_slots[Slot];
|
||||
}
|
||||
// whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level
|
||||
bool scenery_wanted(std::size_t const Slot);
|
||||
// whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level)
|
||||
bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const;
|
||||
// the slot has no vehicles by it: the shading reads the scenery part
|
||||
void no_vehicles(std::size_t const Slot)
|
||||
{
|
||||
m_slots[Slot].vehicles = false;
|
||||
}
|
||||
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
|
||||
// the geometry is to be drawn with the programs of the cube maps and a modelview to world axes centred on the lamp
|
||||
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a lamp
|
||||
void end_lamp(std::uint32_t const Drawcalls);
|
||||
// the timing of the drawing of the maps of the lamps in this frame
|
||||
void lamps_timer_begin();
|
||||
void lamps_timer_end();
|
||||
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
|
||||
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
|
||||
// the headlight_weights of the light of the lamp in the slot, see weights()
|
||||
glm::vec4 lamp_weights(std::size_t const Slot) const;
|
||||
// the vehicles near the camera, which cast the shadows of the lamps (Global.gfx_lights_shadows_vehicles_range), and those by
|
||||
// the lamp of a slot, within its reach; valid until the next call
|
||||
std::vector<TDynamicObject *> const &lamp_vehicles(glm::dvec3 const &Camera);
|
||||
std::vector<TDynamicObject *> const &slot_vehicles(lamp_slot const &Slot);
|
||||
std::vector<TDynamicObject *> const &slot_vehicles() const
|
||||
{
|
||||
return m_slotvehicles;
|
||||
}
|
||||
|
||||
// headlights of vehicles
|
||||
|
||||
// a set of headlights (one end of a vehicle) with a slot
|
||||
struct headlight_slot
|
||||
{
|
||||
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
|
||||
int index{-1};
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
|
||||
bool drawn{false}; // the map holds the shadows of the headlights
|
||||
unsigned int drawnframe{0};
|
||||
// the projector the map was drawn with: world space position, world axes to its axes, projection
|
||||
glm::dvec3 projector{0.0};
|
||||
glm::mat3 rotation{1.f};
|
||||
glm::mat4 projection{1.f};
|
||||
glm::vec3 direction{0.f}; // of the light when the map was drawn
|
||||
};
|
||||
// the headlights which shine, the most important first (in the order the light buffers take them), whose beam may be seen
|
||||
// from the camera of the colour pass within the reach of the shadows, as many as there are to be slots; valid until the next call
|
||||
std::vector<light_array::light_record const *> const &pick_headlights(light_array const &Lights, opengl_camera const &Camera);
|
||||
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
|
||||
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
|
||||
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
|
||||
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
|
||||
void reset_headlights();
|
||||
// slots of the headlights picked in this frame
|
||||
std::vector<std::size_t> const &headlights() const
|
||||
{
|
||||
return m_headlightswanted;
|
||||
}
|
||||
headlight_slot const &headlight(std::size_t const Slot) const
|
||||
{
|
||||
return m_headlightslots[Slot];
|
||||
}
|
||||
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
|
||||
// while the headlights Moved (with the vehicle)
|
||||
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
|
||||
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
|
||||
// to be drawn by the shadow pass with this projection, back faces only
|
||||
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a set of headlights
|
||||
void end_headlight(std::uint32_t const Drawcalls);
|
||||
// the timing of the drawing of the maps of the headlights in this frame
|
||||
void headlights_timer_begin();
|
||||
void headlights_timer_end();
|
||||
// the slot of the headlights with their map ready in this frame, -1 if there is none
|
||||
int headlight_map(TDynamicObject const *Owner, int const Index) const;
|
||||
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
|
||||
glm::vec4 headlight_params(std::size_t const Slot) const;
|
||||
|
||||
// the occupied vehicle whose cab casts the shadows of the lamps and headlights, nullptr when it's not drawn
|
||||
static TDynamicObject *shadow_cab();
|
||||
|
||||
// lines for the statistics of the debug panel
|
||||
std::string info() const;
|
||||
// controls for the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
protected:
|
||||
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
|
||||
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
|
||||
// it doesn't) of the maps of headlights
|
||||
struct level_setup
|
||||
{
|
||||
int size;
|
||||
unsigned int interval;
|
||||
float radius;
|
||||
int sceneryperframe;
|
||||
int headlightsize;
|
||||
unsigned int headlightmoving;
|
||||
unsigned int headlightstill;
|
||||
};
|
||||
static level_setup setup(int const Level);
|
||||
|
||||
// what the backend does with the maps: makes them (false: they can't be made) and lets them go; the cab lamp: one cube, the
|
||||
// lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers
|
||||
virtual bool gpu_allocate(int const Size) = 0;
|
||||
virtual void gpu_release() = 0;
|
||||
virtual bool gpu_allocate_lamps(int const Count, int const Size) = 0;
|
||||
virtual void gpu_release_lamps() = 0;
|
||||
virtual bool gpu_allocate_headlights(int const Count, int const Size) = 0;
|
||||
virtual void gpu_release_headlights() = 0;
|
||||
// the drawing to the map of the cab lamp, to a cube of the lamps (Vehicles: the working copy, from a copy of the scenery part
|
||||
// Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights
|
||||
virtual void gpu_begin_cab() = 0;
|
||||
virtual void gpu_end_cab() = 0;
|
||||
virtual void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) = 0;
|
||||
virtual void gpu_end_lamp() = 0;
|
||||
virtual void gpu_begin_headlight(std::size_t const Slot) = 0;
|
||||
virtual void gpu_end_headlight() = 0;
|
||||
// the gpu times of the drawings of a frame
|
||||
virtual void gpu_lamps_timer(bool const Begin) = 0;
|
||||
virtual void gpu_headlights_timer(bool const Begin) = 0;
|
||||
|
||||
void allocate(int const Size);
|
||||
void release();
|
||||
void allocate_lamps(int const Count, int const Size);
|
||||
void release_lamps();
|
||||
void allocate_headlights(int const Count, int const Size);
|
||||
void release_headlights();
|
||||
|
||||
int m_level{-1}; // level the maps were made for
|
||||
// cab lamp
|
||||
int m_size{0}; // of a face of the map, 0 when there is none
|
||||
bool m_valid{false}; // the map holds the shadows of the lamp of m_vehicle
|
||||
TDynamicObject const *m_vehicle{nullptr};
|
||||
TModel3d const *m_cab{nullptr};
|
||||
glm::vec3 m_lamp{0.f};
|
||||
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
|
||||
// scenery lamps: the scenery parts of the slots first, then their working copies
|
||||
int m_lampssize{0};
|
||||
std::vector<lamp_slot> m_slots;
|
||||
std::vector<std::size_t> m_wanted;
|
||||
unsigned int m_frame{0}; // framestamp of the last update_lamps()
|
||||
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
|
||||
bool m_lampstiming{false};
|
||||
std::vector<TDynamicObject *> m_lampvehicles;
|
||||
std::vector<TDynamicObject *> m_slotvehicles;
|
||||
// headlights: a layer per slot
|
||||
int m_headlightssize{0};
|
||||
std::vector<headlight_slot> m_headlightslots;
|
||||
std::vector<std::size_t> m_headlightswanted;
|
||||
bool m_headlightstiming{false};
|
||||
std::chrono::steady_clock::time_point m_headlightscpustart;
|
||||
std::vector<std::pair<float, light_array::light_record const *>> m_headlightcandidates;
|
||||
std::vector<light_array::light_record const *> m_headlightpicked;
|
||||
// statistics
|
||||
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
||||
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
|
||||
double m_cputime{0.0}; // milliseconds, smoothed
|
||||
std::chrono::steady_clock::time_point m_cpustart;
|
||||
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far
|
||||
std::uint32_t m_lampsvehicles{0}; // working copies drawn so far
|
||||
std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
|
||||
std::uint32_t m_lampsframedrawcalls{0};
|
||||
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
|
||||
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
|
||||
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
|
||||
std::uint32_t m_headlightsframedrawcalls{0};
|
||||
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
@@ -26,7 +26,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/TrackSleepers.h"
|
||||
#include "model/material.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "rendering/common/instancecull.h"
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -230,32 +233,65 @@ class scene_walk
|
||||
virtual void walk_region_begin() {}
|
||||
// the opaque ground of the colour pass is drawn, the editor takes the position of the cursor from the depth
|
||||
virtual void walk_editor_cursor() {}
|
||||
// a bucket of instances of a cell: false when the backend took the instances (the walk leaves them out); Area is the cell's
|
||||
virtual bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// a bucket of instances of a cell: false when the gpu culling took the instances (the walk leaves them out); Area is the cell's
|
||||
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area);
|
||||
// a bucket left out of the pass as wholly out of sight
|
||||
virtual void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) {}
|
||||
// a model drawn on its own: true when the backend took it. Inrange: the cell of the instance lies within the range of the pass
|
||||
virtual bool walk_queue_model(TAnimModel *Instance, bool const Inrange)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the sleepers of a track: true when the backend took them
|
||||
virtual bool walk_queue_sleepers(TTrack *Track)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// a model drawn on its own: true when the gpu culling took it. Inrange: the cell of the instance lies within the range of the pass
|
||||
bool walk_queue_model(TAnimModel *Instance, bool const Inrange);
|
||||
// the sleepers of a track: true when the gpu culling took them
|
||||
bool walk_queue_sleepers(TTrack *Track);
|
||||
// the sleepers the walk drew for a track, by their squared distances, and the lod bounds of their model
|
||||
virtual void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) {}
|
||||
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn);
|
||||
// the instances of the cells are about to be drawn: whether the walk reports what it drew of each bucket (walk_instances_compare())
|
||||
virtual bool walk_instances_begin()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) {}
|
||||
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts);
|
||||
virtual void walk_instances_end() {}
|
||||
// the geometry of the backend and whether it draws blended now, for the draws recorded for the gpu culling
|
||||
virtual gfx::geometrybank_manager &walk_geometry() = 0;
|
||||
virtual bool walk_blending_enabled() const = 0;
|
||||
|
||||
// the gpu culling of the instances (Global.gfx_gpucull, stage N6c): what the walk queues in the culling of the pass and the
|
||||
// draws it records for the indirect draws made from its result. the backend picks the culling of the pass, runs it and
|
||||
// makes the draws
|
||||
// starts the culling of the pass, or none (nullptr). Mainview: the culling of the main view, which takes the sleepers
|
||||
void Begin_Pass_Cull(gfx::instance_cull *Cull, bool const Mainview);
|
||||
// with the cpu drawing (Global.gfx_gpucull 2): what it drew of the sleepers and of the models drawn on their own, for the
|
||||
// comparison with the culling dispatched already
|
||||
void Compare_Pass_Cull();
|
||||
// ends the culling of the pass
|
||||
void End_Pass_Cull();
|
||||
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
|
||||
// Render_Sleepers(), making their records first when they're missing or out of date
|
||||
void Queue_Sleepers(TTrack *Track);
|
||||
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
|
||||
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
|
||||
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
|
||||
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
|
||||
bool Queue_Model(TAnimModel *Instance, bool const Inrange);
|
||||
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
|
||||
// is kept for the rest of the pass
|
||||
bool Instances_Cast_Shadow(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket);
|
||||
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
|
||||
// (m_indirectdraws, m_indirectcommands). Cascade: the pass is one of the cascades of the sun shadows, which share the walks
|
||||
void Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade);
|
||||
// a draw of the walk of Walk_Indirect_Draws(), with the state the walk left for it (model_ubs with the modelview set)
|
||||
void Record_Indirect_Draw(gfx::geometry_handle const &Geometry);
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
|
||||
// same state follow one another, the draws of one entry kept in the order of the walk
|
||||
void Order_Indirect_Draws();
|
||||
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
|
||||
// bank, primitive and blending
|
||||
bool Indirect_Mergeable(std::size_t const First, std::size_t const Other) const;
|
||||
// the texture the shadow material binds for the alpha test of a material, or null_handle (no test)
|
||||
texture_handle Indirect_Shadow_Texture(material_handle const Material) const;
|
||||
// the records of the sleepers and of the models drawn on their own not queued for a while are let go
|
||||
void Expire_Indirect_Records();
|
||||
// a scenery is being loaded: the tracks and the models the records were made for may be gone
|
||||
void Clear_Indirect_Records();
|
||||
|
||||
// members
|
||||
renderpass_config m_renderpass; // parameters for current render pass
|
||||
@@ -320,6 +356,150 @@ class scene_walk
|
||||
scene::basic_cell::instance_bucket_key const *m_modelcomparekey{nullptr};
|
||||
std::vector<std::pair<scene::basic_cell::instance_bucket_key, float>> m_modelcpudistances;
|
||||
bool m_indirectchanging{false}; // the walk under way met a submodel which may look different in the next frame
|
||||
// the gpu culling of the pass under way (Begin_Pass_Cull()), and whether there's one
|
||||
gfx::instance_cull *m_passcull{nullptr};
|
||||
bool m_instancecullpass{false};
|
||||
bool m_modelspass{false}; // the models drawn on their own of the pass under way go to its gpu culling
|
||||
bool m_indirectcolourmodels{false}; // with m_indirectcolourpending: the depth pass culled and recorded them too
|
||||
bool m_sleeperspass{false}; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
|
||||
bool m_indirectcoloursleepers{false}; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
|
||||
// the instances were culled in the depth pass of the whole scene, the colour pass draws them from the recorded draws
|
||||
bool m_indirectcolourpending{false};
|
||||
// Instances_Cast_Shadow() of the groups of the shadow pass under way
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
|
||||
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
|
||||
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
|
||||
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
|
||||
struct sleeper_records
|
||||
{
|
||||
scene::basic_cell::instance_bucket bucket; // the records only, no instances
|
||||
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
|
||||
scene::bounding_area area; // of the locations of the sleepers
|
||||
TModel3d const *model{nullptr}; // what the records were made from
|
||||
material_handle skin{null_handle};
|
||||
glm::mat4 const *transforms{nullptr};
|
||||
std::size_t count{0};
|
||||
unsigned int frame{0}; // queued last
|
||||
};
|
||||
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
|
||||
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
|
||||
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
|
||||
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
|
||||
struct model_records
|
||||
{
|
||||
scene::basic_cell::instance_bucket bucket; // the instance alone
|
||||
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
|
||||
scene::bounding_area area; // the location of the instance
|
||||
unsigned int frame{0}; // queued last
|
||||
};
|
||||
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
|
||||
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
|
||||
// drawn before the culling runs)
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
|
||||
// a draw recorded from the walk of the submodel tree of a group in one lod band (Walk_Indirect_Draws()), to be replayed once
|
||||
// the instances of its command are known: the state the walk left for it and what it bound
|
||||
struct indirect_draw
|
||||
{
|
||||
gl::model_ubs ubs;
|
||||
material_handle material;
|
||||
TSubModel const *submodel;
|
||||
gfx::geometry_handle geometry;
|
||||
unsigned int type; // primitive
|
||||
bool blending; // drawn with the transparency hack of submodels faded out
|
||||
std::uint32_t group{0}; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group())
|
||||
};
|
||||
std::vector<indirect_draw> m_indirectdraws;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
|
||||
bool m_indirectrecording{false}; // the draws and the material binds of the backend record instead of drawing
|
||||
std::uint32_t m_indirectentry{0}; // entry (group and lod band) recorded
|
||||
material_handle m_indirectmaterial{null_handle}; // what the last material bind noted while recording
|
||||
TSubModel const *m_indirectsubmodel{nullptr};
|
||||
// the walks Walk_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the
|
||||
// draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it
|
||||
struct cascade_band
|
||||
{
|
||||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
struct cascade_record
|
||||
{
|
||||
unsigned int frame{0};
|
||||
std::vector<cascade_band> bands;
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
|
||||
std::vector<indirect_draw> m_cascadedraws;
|
||||
std::vector<gl::instance_draw_command> m_cascadecommands;
|
||||
unsigned int m_cascaderecordframe{0};
|
||||
// the walks Walk_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
|
||||
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
|
||||
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
|
||||
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
|
||||
// own (with an instance in the key) are walked every time
|
||||
struct walk_band
|
||||
{
|
||||
std::vector<indirect_draw> draws; // as recorded
|
||||
float bandmin{0.f};
|
||||
std::uint32_t modelversion{0};
|
||||
bool blending{false}; // the state the walk began in
|
||||
bool valid{false};
|
||||
};
|
||||
struct walk_record
|
||||
{
|
||||
unsigned int frame{0}; // used last
|
||||
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
|
||||
int m_walkrankcutoff{0}; // Global.gfx_shadow_rank_cutoff the walks were made with
|
||||
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
|
||||
// of the walks and of putting the draws in order with their blocks uploaded
|
||||
struct walk_counts
|
||||
{
|
||||
std::uint32_t walked{0};
|
||||
std::uint32_t changing{0};
|
||||
std::uint32_t reused{0};
|
||||
double walktime{0.0};
|
||||
double ordertime{0.0};
|
||||
};
|
||||
walk_counts m_walkcounts;
|
||||
walk_counts m_lastwalkcounts;
|
||||
unsigned int m_walkcountsframe{0};
|
||||
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
|
||||
void Replay_Walk(walk_band const &Walk, bool const Shadows);
|
||||
// working space of Order_Indirect_Draws(), kept between frames
|
||||
struct indirect_queue
|
||||
{
|
||||
std::uint32_t next; // draw
|
||||
std::uint32_t end;
|
||||
std::uint32_t link; // next queue waiting for the same state
|
||||
};
|
||||
struct indirect_state
|
||||
{
|
||||
std::uint32_t head;
|
||||
std::uint32_t tail;
|
||||
};
|
||||
// what a multi draw has in common, and the draw which has it
|
||||
struct indirect_state_key
|
||||
{
|
||||
material_handle material;
|
||||
std::uint32_t variant; // of the textures of the material
|
||||
std::uint32_t bank;
|
||||
unsigned int type;
|
||||
std::uint32_t indexed;
|
||||
bool blending;
|
||||
std::uint32_t draw;
|
||||
auto tie() const
|
||||
{
|
||||
return std::tie(material, variant, bank, type, indexed, blending);
|
||||
}
|
||||
};
|
||||
std::vector<indirect_state_key> m_indirectstatekeys;
|
||||
std::vector<std::uint32_t> m_indirectdrawstates;
|
||||
std::vector<indirect_queue> m_indirectqueues;
|
||||
std::vector<indirect_state> m_indirectstates;
|
||||
std::vector<std::uint32_t> m_indirectstateorder;
|
||||
std::vector<std::uint32_t> m_indirectorder;
|
||||
std::vector<indirect_draw> m_indirectdrawsordered;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
|
||||
// a vehicle drawn in the colour pass of this frame moved (motion blur)
|
||||
bool m_vehiclesmoving{false};
|
||||
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil
|
||||
|
||||
829
rendering/common/scenewalkindirect.cpp
Normal file
829
rendering/common/scenewalkindirect.cpp
Normal file
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
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 part of the shared walk of the scene which feeds the gpu culling of the instances and records the draws made from its
|
||||
// result (stage N6c of claude/vulkan14-renderer-plan.md): the records of the sleepers and of the models drawn on their own, the
|
||||
// queueing of buckets, the walks of the submodel trees of the culled groups and the order of the recorded draws
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/scenewalk.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the group of the sleepers of a track in the gpu culling: their model, and the skin in the slot Render_Sleepers() sets it in
|
||||
scene::basic_cell::instance_bucket_key sleeper_key(TTrack const &Track)
|
||||
{
|
||||
scene::basic_cell::instance_bucket_key key;
|
||||
key.pModel = Track.m_sleeper_model;
|
||||
key.skins[1] = Track.m_sleeper_skin;
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void scene_walk::Begin_Pass_Cull(gfx::instance_cull *Cull, bool const Mainview)
|
||||
{
|
||||
m_passcull = Cull;
|
||||
m_instancecullpass = false;
|
||||
if (m_passcull != nullptr)
|
||||
{
|
||||
m_instancecullpass = true;
|
||||
// the same choices as in Render_Instanced()
|
||||
auto const reflections{m_renderpass.draw_mode == rendermode::reflections};
|
||||
gfx::instance_cull::pass_setup setup;
|
||||
setup.frame = m_framestamp;
|
||||
setup.camera = m_renderpass.pass_camera.position();
|
||||
setup.distancecamera = (m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera.position() : m_renderpass.pass_camera.position());
|
||||
setup.frustum = &m_renderpass.pass_camera.frustum();
|
||||
setup.distancescale = (reflections ? 1.f : static_cast<float>(1.0 / (static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.fDistanceFactor))));
|
||||
setup.distanceoffset = (reflections ? static_cast<float>(sq(EU07_REFLECTIONFIDELITYOFFSET)) : 0.f);
|
||||
setup.distancerange = (reflections ? static_cast<float>(sq(Global.reflectiontune.range_instances)) : std::numeric_limits<float>::max());
|
||||
setup.drawrange = static_cast<float>(sq(m_renderpass.draw_range + 250));
|
||||
setup.zoom = Global.ZoomFactor;
|
||||
setup.drawing = (Global.gfx_gpucull == 1);
|
||||
m_passcull->begin_pass(setup);
|
||||
}
|
||||
// the sleepers of tracks go with the instances of the main view
|
||||
m_sleeperspass = (Mainview && (m_passcull != nullptr) && Global.gfx_gpucull_sleepers);
|
||||
if (m_sleeperspass && (false == m_passcull->drawing()))
|
||||
{
|
||||
for (auto &counts : m_sleeperbandcounts)
|
||||
{
|
||||
counts.second.clear();
|
||||
}
|
||||
}
|
||||
// the models drawn on their own go with the instances of every pass culled on the gpu
|
||||
m_modelspass = (m_instancecullpass && Global.gfx_gpucull_models);
|
||||
}
|
||||
|
||||
void scene_walk::Compare_Pass_Cull()
|
||||
{
|
||||
// the sleepers the cpu drew along the tracks, before the culling ran
|
||||
if (m_sleeperspass)
|
||||
{
|
||||
for (auto const &counts : m_sleeperbandcounts)
|
||||
{
|
||||
if (false == counts.second.empty())
|
||||
{
|
||||
m_passcull->cpu_result(counts.first, counts.second, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// and the models drawn on their own
|
||||
for (auto const &model : m_modelcpudistances)
|
||||
{
|
||||
m_passcull->cpu_instance(model.first, model.second);
|
||||
}
|
||||
}
|
||||
|
||||
void scene_walk::End_Pass_Cull()
|
||||
{
|
||||
if (m_instancecullpass)
|
||||
{
|
||||
m_passcull->end_pass();
|
||||
m_instancecullpass = false;
|
||||
m_passcull = nullptr;
|
||||
}
|
||||
m_sleeperspass = false;
|
||||
m_modelspass = false;
|
||||
}
|
||||
|
||||
bool scene_walk::walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
|
||||
{
|
||||
if (m_instancecullpass)
|
||||
{
|
||||
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
|
||||
// sun shadows are left out of them, as Render_Instanced() leaves them
|
||||
if ((m_renderpass.draw_mode != rendermode::shadows) || Instances_Cast_Shadow(Key, Bucket))
|
||||
{
|
||||
m_passcull->add_bucket(Key, Bucket, (Bucketrange ? Bucket_Area(Bucket) : Area));
|
||||
}
|
||||
// and when the instances are drawn from its result, the cpu has nothing more to do with them
|
||||
if (m_passcull->drawing())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// the depth pass of the whole scene culled them already, the colour pass draws what it recorded
|
||||
if (m_indirectcolourpending)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void scene_walk::walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
if (m_instancecullpass)
|
||||
{
|
||||
m_passcull->skip_bucket(Bucket);
|
||||
}
|
||||
}
|
||||
|
||||
bool scene_walk::walk_queue_model(TAnimModel *Instance, bool const Inrange)
|
||||
{
|
||||
return Queue_Model(Instance, Inrange);
|
||||
}
|
||||
|
||||
bool scene_walk::walk_queue_sleepers(TTrack *Track)
|
||||
{
|
||||
if (m_sleeperspass)
|
||||
{
|
||||
// culled with the instances of the pass and drawn from the result, or drawn by the cpu as well to compare
|
||||
Queue_Sleepers(Track);
|
||||
if (m_passcull->drawing())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (m_indirectcolourpending && m_indirectcoloursleepers)
|
||||
{
|
||||
// the depth pass of the whole scene culled and recorded them, the colour pass draws what it recorded
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void scene_walk::walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn)
|
||||
{
|
||||
if (m_sleeperspass && m_instancecullpass && (false == m_passcull->drawing()))
|
||||
{
|
||||
// the comparison with the gpu culling: the sleepers drawn by lod band, the bands as the culling makes them, split by
|
||||
// the bounds of the model past 0 and short of the largest float
|
||||
auto const counted = [](float const Bound) {
|
||||
return (Bound > 0.f) && (Bound < std::numeric_limits<float>::max());
|
||||
};
|
||||
auto &counts{m_sleeperbandcounts[sleeper_key(Track)]};
|
||||
auto const bands{static_cast<std::size_t>(std::count_if(Lodbounds.begin(), Lodbounds.end(), counted)) + 1};
|
||||
if (counts.size() < bands)
|
||||
{
|
||||
counts.resize(bands, 0);
|
||||
}
|
||||
for (auto const &survivor : Drawn)
|
||||
{
|
||||
std::size_t band{0};
|
||||
for (auto const bound : Lodbounds)
|
||||
{
|
||||
if (counted(bound) && (bound <= survivor.first))
|
||||
{
|
||||
++band;
|
||||
}
|
||||
}
|
||||
++counts[band];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scene_walk::walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts)
|
||||
{
|
||||
m_passcull->cpu_result(Key, Bandcounts);
|
||||
}
|
||||
|
||||
void scene_walk::Queue_Sleepers(TTrack *Track)
|
||||
{
|
||||
// the gates of Render_Sleepers(); the range is tested for the whole track
|
||||
if ((Track->m_sleeper_model == nullptr) || Track->m_sleeper_local_transforms.empty() || (Global.SleeperDistance <= 0.f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const distsq{glm::length2(Track->location() - m_renderpass.pass_camera.position())};
|
||||
auto const cutoffsq{static_cast<double>(Global.SleeperDistance) * static_cast<double>(Global.SleeperDistance)};
|
||||
if (distsq > cutoffsq)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto &records{m_sleeperrecords[Track]};
|
||||
auto const &transforms{Track->m_sleeper_local_transforms};
|
||||
if ((records.bucket.recordsversion != m_instancerecordsversion) || (records.model != Track->m_sleeper_model) || (records.skin != Track->m_sleeper_skin) ||
|
||||
(records.transforms != transforms.data()) || (records.count != transforms.size()))
|
||||
{
|
||||
// each sleeper is a record placed and turned by its transform, relative to the origin of the track (the centre of its
|
||||
// cell), with the sphere Render_Sleepers() tests against the frustum
|
||||
auto const radius{std::max(Track->m_sleeper_model->bounding_radius(), 2.0f)};
|
||||
auto &bucket{records.bucket};
|
||||
bucket.records.clear();
|
||||
bucket.records.reserve(transforms.size());
|
||||
glm::vec3 low{std::numeric_limits<float>::max()};
|
||||
glm::vec3 high{std::numeric_limits<float>::lowest()};
|
||||
for (auto const &local : transforms)
|
||||
{
|
||||
scene::basic_cell::instance_record record;
|
||||
glm::vec3 const position{local[3]};
|
||||
record.position = packed_vec3{position};
|
||||
record.radius = radius;
|
||||
record.rangesquaredmin = 0.f;
|
||||
record.rangesquaredmax = std::numeric_limits<float>::max();
|
||||
record.flags = scene::basic_cell::instance_visible | scene::basic_cell::instance_instanceable;
|
||||
for (int column = 0; column < 3; ++column)
|
||||
{
|
||||
record.rotationscale[column] = local[column];
|
||||
}
|
||||
bucket.records.emplace_back(record);
|
||||
low = glm::min(low, position);
|
||||
high = glm::max(high, position);
|
||||
}
|
||||
bucket.recordorigin = Track->m_origin;
|
||||
bucket.lodrangesquared = std::numeric_limits<float>::max();
|
||||
bucket.recordsversion = m_instancerecordsversion;
|
||||
bucket.gpustale = true;
|
||||
records.key = sleeper_key(*Track);
|
||||
records.area = scene::bounding_area{Track->m_origin + glm::dvec3{(low + high) * 0.5f}, 0.5f * glm::length(high - low)};
|
||||
records.model = Track->m_sleeper_model;
|
||||
records.skin = Track->m_sleeper_skin;
|
||||
records.transforms = transforms.data();
|
||||
records.count = transforms.size();
|
||||
}
|
||||
records.frame = m_framestamp;
|
||||
m_passcull->add_sleepers(records.key, records.bucket, records.area);
|
||||
}
|
||||
|
||||
// queues a model instance drawn on its own in the gpu culling of the pass, as a bucket and a group of its own. the culling
|
||||
// makes the tests of Render( TAnimModel * ): visibility, frustum, the range of the instance, the draw range, the size on the
|
||||
// screen, and gives it the lod band of the bounds of its submodels (mindistance/maxdistance) its distance falls in, then
|
||||
// Walk_Indirect_Draws() animates, prepares and walks its submodel tree from its origin, like it does for a group of
|
||||
// instanced ones
|
||||
bool scene_walk::Queue_Model(TAnimModel *Instance, bool const Inrange)
|
||||
{
|
||||
// a billboard is turned to the camera from the modelview of the instance, which a walk from its origin doesn't have
|
||||
if ((false == Instance->m_cameraindependent) || (Instance->Model() == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (false == m_modelspass)
|
||||
{
|
||||
// the depth pass of the whole scene culled and recorded it, the colour pass draws what it recorded
|
||||
return (m_indirectcolourpending && m_indirectcolourmodels);
|
||||
}
|
||||
auto const drawing{m_passcull->drawing()};
|
||||
// a model hidden by an event, or in a cell wholly out of the range of the pass, would be left out by the culling
|
||||
if ((false == Instance->visible()) || (false == Inrange))
|
||||
{
|
||||
return drawing;
|
||||
}
|
||||
|
||||
auto &records{m_modelrecords[Instance]};
|
||||
auto &bucket{records.bucket};
|
||||
if ((bucket.recordsversion != m_instancerecordsversion) || (bucket.records.size() != 1))
|
||||
{
|
||||
// the key, the instance and the record anew: the editor may have given the instance another model or skins, or
|
||||
// another instance may have taken the place of a deleted one
|
||||
auto &key{records.key};
|
||||
key = {};
|
||||
key.pModel = Instance->Model();
|
||||
if (auto const *material{Instance->Material()})
|
||||
{
|
||||
for (int skin = 0; skin < 5; ++skin)
|
||||
{
|
||||
key.skins[skin] = material->replacable_skins[skin];
|
||||
}
|
||||
}
|
||||
key.instance = Instance;
|
||||
bucket.instances.assign(1, Instance);
|
||||
// the location of the instance as the origin of the record, which keeps the precision
|
||||
Update_Instance_Records(key.pModel, bucket, Instance->location());
|
||||
// drawn by its bucket, though it isn't instanceable
|
||||
bucket.records.front().flags |= scene::basic_cell::instance_instanceable;
|
||||
records.area = scene::bounding_area{Instance->location(), 0.f};
|
||||
}
|
||||
records.frame = m_framestamp;
|
||||
if (m_renderpass.draw_mode == rendermode::shadows)
|
||||
{
|
||||
// a model with nothing to draw in the shadows is left out of them, like the instances. the answer depends on the
|
||||
// model and the skins only, and is shared by the instances with them
|
||||
auto modelkey{records.key};
|
||||
modelkey.instance = nullptr;
|
||||
if (false == Instances_Cast_Shadow(modelkey, bucket))
|
||||
{
|
||||
return drawing;
|
||||
}
|
||||
}
|
||||
// out of the frustum of the pass, beyond its range or too small on the screen by its record: the culling would leave it out,
|
||||
// and the walk of Walk_Indirect_Draws() isn't made for it
|
||||
if (Global.gfx_gpucull_bucketrange && (false == EditorModeFlag) && Bucket_Culled(bucket))
|
||||
{
|
||||
m_passcull->skip_bucket(bucket);
|
||||
return drawing;
|
||||
}
|
||||
m_passcull->add_bucket(records.key, bucket, records.area);
|
||||
if (false == drawing)
|
||||
{
|
||||
// the cpu draws it too, and tells what it drew for the comparison
|
||||
m_modelcomparekey = &records.key;
|
||||
}
|
||||
return drawing;
|
||||
}
|
||||
|
||||
bool scene_walk::Instances_Cast_Shadow(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
auto const lookup{m_shadowcasters.find(Key)};
|
||||
if (lookup != m_shadowcasters.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
|
||||
// the skins of the instance Render_Instanced() would take them from
|
||||
TAnimModel const *instance{nullptr};
|
||||
for (auto const *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto const *skins{instance != nullptr && instance->Material() != nullptr ? instance->Material()->replacable_skins : nullptr};
|
||||
auto castsshadow{false};
|
||||
m_instance_lodpending.clear();
|
||||
m_instance_lodpending.push_back(Key.pModel != nullptr ? Key.pModel->Root : nullptr);
|
||||
while ((false == castsshadow) && (false == m_instance_lodpending.empty()))
|
||||
{
|
||||
auto const *submodel = m_instance_lodpending.back();
|
||||
m_instance_lodpending.pop_back();
|
||||
if (submodel == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (submodel->eType < TP_ROTATOR)
|
||||
{
|
||||
auto const material{submodel->m_material >= 0 ? submodel->m_material : (skins != nullptr ? skins[-submodel->m_material] : null_handle)};
|
||||
castsshadow = (walk_material_data(material).shadow_rank <= Global.gfx_shadow_rank_cutoff);
|
||||
}
|
||||
m_instance_lodpending.push_back(submodel->Child);
|
||||
m_instance_lodpending.push_back(submodel->Next);
|
||||
}
|
||||
m_shadowcasters.emplace(Key, castsshadow);
|
||||
return castsshadow;
|
||||
}
|
||||
|
||||
void scene_walk::Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade)
|
||||
{
|
||||
m_indirectdraws.clear();
|
||||
m_indirectcommands.clear();
|
||||
auto const &lodbounds{Cull.lod_bounds()};
|
||||
// the cascades of the sun shadows make the same walks: the mode, the lod band and the state of the models are the same in all
|
||||
// of them within a frame, only the instances differ, and those the gpu fills in. the walk of a group in a band is made by the
|
||||
// first cascade which draws it, the others copy its draws and commands with entries of their own
|
||||
auto const cascade{(m_renderpass.draw_mode == rendermode::shadows) && Cascade};
|
||||
auto constexpr nowalk{std::numeric_limits<std::uint32_t>::max()};
|
||||
if (cascade && (m_cascaderecordframe != m_framestamp))
|
||||
{
|
||||
m_cascaderecordframe = m_framestamp;
|
||||
m_cascadedraws.clear();
|
||||
m_cascadecommands.clear();
|
||||
if ((m_framestamp % 256) == 0)
|
||||
{
|
||||
// the groups not drawn for a while
|
||||
for (auto records{m_cascaderecords.begin()}; records != m_cascaderecords.end();)
|
||||
{
|
||||
if (m_framestamp - records->second.frame > 256)
|
||||
{
|
||||
records = m_cascaderecords.erase(records);
|
||||
}
|
||||
else
|
||||
{
|
||||
++records;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_walkcountsframe != m_framestamp)
|
||||
{
|
||||
m_walkcountsframe = m_framestamp;
|
||||
m_lastwalkcounts = m_walkcounts;
|
||||
m_walkcounts = {};
|
||||
}
|
||||
auto const walkstart{std::chrono::steady_clock::now()};
|
||||
// the walks of earlier frames. they go when the switch is off, as the models may change meanwhile without the walks noticing
|
||||
// it the next time (the season), and when the shadows take other submodels
|
||||
auto const walkcache{Global.gfx_gpucull_walkcache && (false == m_previewing)};
|
||||
if ((false == Global.gfx_gpucull_walkcache) || (m_walkrankcutoff != Global.gfx_shadow_rank_cutoff))
|
||||
{
|
||||
m_walkrecords.clear();
|
||||
m_walkrankcutoff = Global.gfx_shadow_rank_cutoff;
|
||||
}
|
||||
if (walkcache && ((m_framestamp % 256) == 0))
|
||||
{
|
||||
// the groups not drawn for a while
|
||||
for (auto records{m_walkrecords.begin()}; records != m_walkrecords.end();)
|
||||
{
|
||||
if (m_framestamp - records->second.frame > 256)
|
||||
{
|
||||
records = m_walkrecords.erase(records);
|
||||
}
|
||||
else
|
||||
{
|
||||
++records;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
|
||||
m_indirectrecording = true;
|
||||
for (auto const &group : Cull.draw_groups())
|
||||
{
|
||||
auto *model{group.key->pModel};
|
||||
if ((model == nullptr) || (model->Root == nullptr) || ((group.instance == nullptr) && (false == group.sleepers)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// a model drawn on its own is prepared with its lights and animations each time; the others are prepared only for a walk.
|
||||
// the version is taken before, so a walk prepared by something which changes the model isn't taken up again
|
||||
walk_record *kept{nullptr};
|
||||
if (walkcache && (group.key->instance == nullptr))
|
||||
{
|
||||
kept = &m_walkrecords[*group.key];
|
||||
kept->frame = m_framestamp;
|
||||
auto &bands{kept->bands[shadows ? 1 : 0]};
|
||||
if (bands.size() != group.lodcount + 1)
|
||||
{
|
||||
bands.clear();
|
||||
bands.resize(group.lodcount + 1);
|
||||
}
|
||||
}
|
||||
auto const modelversion{model->state_version()};
|
||||
material_handle const *skins{nullptr};
|
||||
auto alpha{0x30300030};
|
||||
auto prepared{false};
|
||||
auto const prepare = [&]() {
|
||||
prepared = true;
|
||||
if (group.sleepers)
|
||||
{
|
||||
// as Render_Sleepers() sets them up: the skin of the track, if any. like scenery models they pick no texture
|
||||
// variants of their own
|
||||
if (group.key->skins[1] != null_handle)
|
||||
{
|
||||
skins = group.key->skins.data();
|
||||
}
|
||||
TSubModel::iVariantSeed = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (group.key->instance != nullptr)
|
||||
{
|
||||
// a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up
|
||||
group.instance->RaAnimate(m_framestamp);
|
||||
}
|
||||
group.instance->RaPrepare();
|
||||
auto const *material{group.instance->Material()};
|
||||
if (material != nullptr)
|
||||
{
|
||||
skins = material->replacable_skins;
|
||||
alpha = material->textures_alpha;
|
||||
}
|
||||
}
|
||||
alpha ^= 0x0F0F000F;
|
||||
};
|
||||
// the walks of the group the cascades made in this frame. a group whose bands were all walked already isn't prepared
|
||||
cascade_record *walks{nullptr};
|
||||
if (cascade)
|
||||
{
|
||||
walks = &m_cascaderecords[*group.key];
|
||||
if ((walks->frame != m_framestamp) || (walks->bands.size() != group.lodcount + 1))
|
||||
{
|
||||
walks->frame = m_framestamp;
|
||||
walks->bands.assign(group.lodcount + 1, cascade_band{nowalk, 0});
|
||||
}
|
||||
}
|
||||
else if (kept == nullptr)
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
for (std::uint32_t band = 0; band <= group.lodcount; ++band)
|
||||
{
|
||||
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
|
||||
auto const bandmin{band == 0 ? 0.f : lodbounds[group.lodfirst + band - 1]};
|
||||
auto const bandmax{band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[group.lodfirst + band]};
|
||||
if ((bandmin > group.distancemax) || (bandmax <= group.distancemin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_indirectentry = group.entryfirst + band;
|
||||
if ((walks != nullptr) && (walks->bands[band].first != nowalk))
|
||||
{
|
||||
// walked by another cascade: its draws, for the instances of this entry
|
||||
auto const &walk{walks->bands[band]};
|
||||
m_indirectdraws.insert(m_indirectdraws.end(), m_cascadedraws.begin() + walk.first, m_cascadedraws.begin() + walk.first + walk.count);
|
||||
for (std::uint32_t draw = 0; draw < walk.count; ++draw)
|
||||
{
|
||||
auto &command{m_indirectcommands.emplace_back(m_cascadecommands[walk.first + draw])};
|
||||
command.entry = m_indirectentry;
|
||||
}
|
||||
// as the walk would count them
|
||||
m_renderpass.draw_stats.submodels += static_cast<int>(walk.count);
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>(walk.count);
|
||||
continue;
|
||||
}
|
||||
auto const walkfirst{m_indirectdraws.size()};
|
||||
auto *keptwalk{kept != nullptr ? &kept->bands[shadows ? 1 : 0][band] : nullptr};
|
||||
if ((keptwalk != nullptr) && (keptwalk->valid) && (keptwalk->bandmin == bandmin) && (keptwalk->modelversion == modelversion) &&
|
||||
(keptwalk->blending == walk_blending_enabled()))
|
||||
{
|
||||
// walked in an earlier frame, with the model as it is now
|
||||
Replay_Walk(*keptwalk, shadows);
|
||||
++m_walkcounts.reused;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == prepared)
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
auto const blending{walk_blending_enabled()};
|
||||
m_indirectchanging = false;
|
||||
::glPushMatrix();
|
||||
::glLoadIdentity();
|
||||
model->Root->fSquareDist = bandmin;
|
||||
model->Root->ReplacableSet(skins, alpha);
|
||||
model->Root->pRoot = model;
|
||||
Render(model->Root);
|
||||
::glPopMatrix();
|
||||
++m_walkcounts.walked;
|
||||
if (m_indirectchanging)
|
||||
{
|
||||
++m_walkcounts.changing;
|
||||
}
|
||||
if (keptwalk != nullptr)
|
||||
{
|
||||
keptwalk->valid = (false == m_indirectchanging);
|
||||
if (keptwalk->valid)
|
||||
{
|
||||
keptwalk->draws.assign(m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
|
||||
keptwalk->bandmin = bandmin;
|
||||
keptwalk->modelversion = modelversion;
|
||||
keptwalk->blending = blending;
|
||||
}
|
||||
else
|
||||
{
|
||||
keptwalk->draws.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (walks != nullptr)
|
||||
{
|
||||
walks->bands[band] = cascade_band{static_cast<std::uint32_t>(m_cascadedraws.size()), static_cast<std::uint32_t>(m_indirectdraws.size() - walkfirst)};
|
||||
m_cascadedraws.insert(m_cascadedraws.end(), m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
|
||||
m_cascadecommands.insert(m_cascadecommands.end(), m_indirectcommands.begin() + walkfirst, m_indirectcommands.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
m_indirectrecording = false;
|
||||
m_walkcounts.walktime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - walkstart).count();
|
||||
}
|
||||
|
||||
void scene_walk::Record_Indirect_Draw(gfx::geometry_handle const &Geometry)
|
||||
{
|
||||
// the walk of Walk_Indirect_Draws(): the draw is made later, from the gpu culling, with this state
|
||||
auto &geometry{walk_geometry()};
|
||||
gfx::indirect_source source;
|
||||
if (geometry.indirect_source(Geometry, source))
|
||||
{
|
||||
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
|
||||
m_indirectdraws.push_back({model_ubs, m_indirectmaterial, m_indirectsubmodel, Geometry, source.type, walk_blending_enabled(), geometry.draw_group(Geometry)});
|
||||
}
|
||||
}
|
||||
|
||||
void scene_walk::Replay_Walk(walk_band const &Walk, bool const Shadows)
|
||||
{
|
||||
auto &geometry{walk_geometry()};
|
||||
for (auto const &walkdraw : Walk.draws)
|
||||
{
|
||||
// the place of the chunk in its bank is asked for again: the bank may have been let go since and made anew, which the
|
||||
// question (as the walk) takes care of, uploading the chunk
|
||||
gfx::indirect_source source;
|
||||
if (false == geometry.indirect_source(walkdraw.geometry, source))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
|
||||
auto &draw{m_indirectdraws.emplace_back(walkdraw)};
|
||||
draw.type = source.type;
|
||||
draw.group = geometry.draw_group(walkdraw.geometry);
|
||||
// the walk sets the placement of the submodel, and before each draw of the colour passes the parameters of its material
|
||||
// (the material bind); the rest of the block is what the pass has at the moment, as the walk would leave it
|
||||
auto const &walked{walkdraw.ubs};
|
||||
draw.ubs = model_ubs;
|
||||
draw.ubs.modelview = walked.modelview;
|
||||
draw.ubs.modelviewnormal = walked.modelviewnormal;
|
||||
if (false == Shadows)
|
||||
{
|
||||
std::memcpy(&draw.ubs.param[0], &walked.param[0], sizeof(draw.ubs.param));
|
||||
draw.ubs.opacity = walked.opacity;
|
||||
draw.ubs.alpha_mult = walked.alpha_mult;
|
||||
}
|
||||
}
|
||||
// as the walk would count them
|
||||
m_renderpass.draw_stats.submodels += static_cast<int>(Walk.draws.size());
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>(Walk.draws.size());
|
||||
}
|
||||
|
||||
texture_handle scene_walk::Indirect_Shadow_Texture(material_handle const Material) const
|
||||
{
|
||||
if (Material == null_handle)
|
||||
{
|
||||
return null_handle;
|
||||
}
|
||||
return walk_material_data(Material).textures[0];
|
||||
}
|
||||
|
||||
// the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same
|
||||
// state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry,
|
||||
// the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on
|
||||
// the later one being drawn over the earlier one. the draws of different entries are drawn for different instances, which
|
||||
// the gpu draws in no particular order anyway, so they're free to move. the entries are queues of draws; the draws at the
|
||||
// heads of the queues which have the state taken next all go, each queue as far as its draws keep that state, then the
|
||||
// queues wait for the state of their next draw. the states go in the order in which the queues first reach them
|
||||
void scene_walk::Order_Indirect_Draws()
|
||||
{
|
||||
auto const count{m_indirectdraws.size()};
|
||||
if (count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// a number for each state the draws have, by sorting the states (the buffers are kept between frames)
|
||||
m_indirectstatekeys.clear();
|
||||
// the sun shadows draw with the program and the texture of the alpha test, whatever the material
|
||||
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
auto const &draw{m_indirectdraws[index]};
|
||||
if (shadows)
|
||||
{
|
||||
m_indirectstatekeys.push_back({Indirect_Shadow_Texture(draw.material), 0u, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
|
||||
continue;
|
||||
}
|
||||
auto variant{0u};
|
||||
if (draw.material != null_handle)
|
||||
{
|
||||
auto const &material{walk_material_data(draw.material)};
|
||||
if (false == material.texture_variants.empty())
|
||||
{
|
||||
variant = TSubModel::VariantId(draw.submodel) % static_cast<std::uint32_t>(material.texture_variants.size());
|
||||
}
|
||||
}
|
||||
m_indirectstatekeys.push_back({draw.material, variant, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
|
||||
}
|
||||
std::sort(std::begin(m_indirectstatekeys), std::end(m_indirectstatekeys), [](indirect_state_key const &Left, indirect_state_key const &Right) { return Left.tie() < Right.tie(); });
|
||||
m_indirectdrawstates.resize(count);
|
||||
std::uint32_t statecount{0};
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
if ((index > 0) && (m_indirectstatekeys[index].tie() != m_indirectstatekeys[index - 1].tie()))
|
||||
{
|
||||
++statecount;
|
||||
}
|
||||
m_indirectdrawstates[m_indirectstatekeys[index].draw] = statecount;
|
||||
}
|
||||
++statecount;
|
||||
if (statecount == count)
|
||||
{
|
||||
// no two draws share a state, nothing would go together
|
||||
return;
|
||||
}
|
||||
|
||||
auto constexpr none{std::numeric_limits<std::uint32_t>::max()};
|
||||
m_indirectstates.assign(statecount, indirect_state{none, none});
|
||||
m_indirectstateorder.clear();
|
||||
m_indirectqueues.clear();
|
||||
// the queue waits for the state of its next draw, behind the queues waiting for it already
|
||||
auto const enqueue = [this, none](std::uint32_t const Queue) {
|
||||
auto &queue{m_indirectqueues[Queue]};
|
||||
auto &state{m_indirectstates[m_indirectdrawstates[queue.next]]};
|
||||
queue.link = none;
|
||||
if (state.head == none)
|
||||
{
|
||||
state.head = Queue;
|
||||
m_indirectstateorder.push_back(m_indirectdrawstates[queue.next]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_indirectqueues[state.tail].link = Queue;
|
||||
}
|
||||
state.tail = Queue;
|
||||
};
|
||||
for (std::size_t index = 0; index < count;)
|
||||
{
|
||||
auto end{index + 1};
|
||||
while ((end < count) && (m_indirectcommands[end].entry == m_indirectcommands[index].entry))
|
||||
{
|
||||
++end;
|
||||
}
|
||||
m_indirectqueues.push_back({static_cast<std::uint32_t>(index), static_cast<std::uint32_t>(end), none});
|
||||
index = end;
|
||||
}
|
||||
for (std::uint32_t queue = 0; queue < m_indirectqueues.size(); ++queue)
|
||||
{
|
||||
enqueue(queue);
|
||||
}
|
||||
m_indirectorder.clear();
|
||||
for (std::size_t taken = 0; taken < m_indirectstateorder.size(); ++taken)
|
||||
{
|
||||
auto const stateindex{m_indirectstateorder[taken]};
|
||||
auto queueindex{m_indirectstates[stateindex].head};
|
||||
m_indirectstates[stateindex] = {none, none};
|
||||
while (queueindex != none)
|
||||
{
|
||||
auto &queue{m_indirectqueues[queueindex]};
|
||||
auto const nextqueue{queue.link};
|
||||
while ((queue.next < queue.end) && (m_indirectdrawstates[queue.next] == stateindex))
|
||||
{
|
||||
m_indirectorder.push_back(queue.next);
|
||||
++queue.next;
|
||||
}
|
||||
if (queue.next < queue.end)
|
||||
{
|
||||
enqueue(queueindex);
|
||||
}
|
||||
queueindex = nextqueue;
|
||||
}
|
||||
}
|
||||
|
||||
m_indirectdrawsordered.clear();
|
||||
m_indirectcommandsordered.clear();
|
||||
for (auto const index : m_indirectorder)
|
||||
{
|
||||
m_indirectdrawsordered.push_back(m_indirectdraws[index]);
|
||||
m_indirectcommandsordered.push_back(m_indirectcommands[index]);
|
||||
}
|
||||
m_indirectdraws.swap(m_indirectdrawsordered);
|
||||
m_indirectcommands.swap(m_indirectcommandsordered);
|
||||
}
|
||||
|
||||
bool scene_walk::Indirect_Mergeable(std::size_t const First, std::size_t const Other) const
|
||||
{
|
||||
auto const &first{m_indirectdraws[First]};
|
||||
auto const &other{m_indirectdraws[Other]};
|
||||
if ((first.material != other.material) || (first.blending != other.blending) || (first.type != other.type) || (first.group != other.group) ||
|
||||
(m_indirectcommands[First].indexed != m_indirectcommands[Other].indexed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (first.material == null_handle)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// the texture set the material bind binds
|
||||
auto const &material{walk_material_data(first.material)};
|
||||
if (material.texture_variants.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (&material.GetTextures(TSubModel::VariantId(first.submodel)) == &material.GetTextures(TSubModel::VariantId(other.submodel)));
|
||||
}
|
||||
|
||||
void scene_walk::Expire_Indirect_Records()
|
||||
{
|
||||
if ((m_framestamp % 256) == 0)
|
||||
{
|
||||
// the sleeper records of tracks out of sight for a while. their places in the record buffer of the gpu culling are
|
||||
// dropped when it starts over
|
||||
for (auto records{m_sleeperrecords.begin()}; records != m_sleeperrecords.end();)
|
||||
{
|
||||
if (m_framestamp - records->second.frame > 256)
|
||||
{
|
||||
records = m_sleeperrecords.erase(records);
|
||||
}
|
||||
else
|
||||
{
|
||||
++records;
|
||||
}
|
||||
}
|
||||
// and so are the records of the models drawn on their own
|
||||
for (auto records{m_modelrecords.begin()}; records != m_modelrecords.end();)
|
||||
{
|
||||
if (m_framestamp - records->second.frame > 256)
|
||||
{
|
||||
records = m_modelrecords.erase(records);
|
||||
}
|
||||
else
|
||||
{
|
||||
++records;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scene_walk::Clear_Indirect_Records()
|
||||
{
|
||||
m_sleeperrecords.clear();
|
||||
m_modelrecords.clear();
|
||||
m_cascaderecords.clear();
|
||||
m_walkrecords.clear();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
273
rendering/common/shadowfit.cpp
Normal file
273
rendering/common/shadowfit.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/shadowfit.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "rendering/openglcamera.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
|
||||
// get any closer to the camera than this, however close the pixels are
|
||||
float const SPLIT_NEAR_MIN{0.5f};
|
||||
float const SPLIT_FAR_MIN{16.f};
|
||||
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
|
||||
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
|
||||
// has to be within its map
|
||||
float const MARGIN_METRES{1.f};
|
||||
float const MARGIN_SHARE{0.05f};
|
||||
|
||||
} // namespace
|
||||
|
||||
gl::shadow_fit_data shadow_fit::begin_measurement(view const &View, unsigned int const Frame)
|
||||
{
|
||||
// numbers start from 1, 0 is no measurement
|
||||
if (++m_measurement == 0)
|
||||
++m_measurement;
|
||||
gl::shadow_fit_data data{};
|
||||
data.measurement = m_measurement;
|
||||
data.depthmin = std::numeric_limits<float>::infinity();
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
for (std::size_t axis = 0; axis < 4; ++axis)
|
||||
{
|
||||
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
|
||||
data.bounds[cascade * 8 + 4 + axis] = 0u;
|
||||
}
|
||||
}
|
||||
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
|
||||
return data;
|
||||
}
|
||||
|
||||
glm::vec4 shadow_fit::bounds_params(view const &View)
|
||||
{
|
||||
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
|
||||
return glm::vec4{View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f)};
|
||||
}
|
||||
|
||||
glm::vec4 shadow_fit::margin_params()
|
||||
{
|
||||
return glm::vec4{MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f};
|
||||
}
|
||||
|
||||
void shadow_fit::arrived(gl::shadow_fit_data const &Data)
|
||||
{
|
||||
auto const &source{m_pending[Data.measurement % PENDING]};
|
||||
if ((Data.measurement != 0) && (source.measurement == Data.measurement))
|
||||
m_latest = result{Data, source.source, source.frame};
|
||||
}
|
||||
|
||||
float shadow_fit::unorder(std::uint32_t const Value)
|
||||
{
|
||||
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
|
||||
float result;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 shadow_fit::light_vector(glm::vec3 const &Sun)
|
||||
{
|
||||
// cap the vertical angle to keep shadows from getting too long
|
||||
return glm::normalize(glm::vec3{Sun.x, std::min(Sun.y, Global.gfx_shadow_angle_min), Sun.z});
|
||||
}
|
||||
|
||||
glm::mat3 shadow_fit::light_rotation(glm::vec3 const &Sun)
|
||||
{
|
||||
// the rotation of the view of the shadow pass (setup_pass())
|
||||
return glm::mat3{glm::lookAt(glm::vec3{0.f}, light_vector(Sun), glm::vec3{0.f, 1.f, 0.f})};
|
||||
}
|
||||
|
||||
std::array<float, gl::MAX_CASCADES + 1> shadow_fit::fixed_splits()
|
||||
{
|
||||
static_assert(gl::MAX_CASCADES == 3, "the fixed cascades are made for 3 of them");
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits{0.0f, Global.shadowtune.range / 32, Global.shadowtune.range / 8, Global.shadowtune.range};
|
||||
if (Global.shadowtune.map_size > 2048)
|
||||
{
|
||||
// increase coverage if our shadow map is large enough to produce decent results
|
||||
for (std::size_t idx = 1; idx < splits.size(); ++idx)
|
||||
splits[idx] *= Global.shadowtune.map_size / 2048;
|
||||
}
|
||||
return splits;
|
||||
}
|
||||
|
||||
char const *shadow_fit::unavailable(bool const Postprocessing, bool const Shadow, std::size_t const Shadowviews)
|
||||
{
|
||||
if (false == Global.gfx_shadowmap_enabled)
|
||||
return "no shadow maps";
|
||||
if (Global.bWireFrame)
|
||||
return "wireframe";
|
||||
// the shadows follow the camera, the view shows another one
|
||||
if (DebugCameraFlag)
|
||||
return "debug camera";
|
||||
if (false == Postprocessing)
|
||||
return "no post processing";
|
||||
if (EditorModeFlag && Global.EditorOrtho)
|
||||
return "orthographic view";
|
||||
if ((false == Shadow) || (Shadowviews != 1))
|
||||
return "the shadows cover more than one view";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool shadow_fit::fit(unsigned int const Frame, opengl_camera const &Camera, char const *Unavailable, std::array<float, gl::MAX_CASCADES + 1> &Splits, areas &Areas, fit_state &State) const
|
||||
{
|
||||
// a measurement older than this many frames, made with the camera this many metres away or the view turned this many
|
||||
// degrees from the one now, isn't used
|
||||
constexpr unsigned int AGE_MAX{6};
|
||||
constexpr float SHIFT_MAX{25.f};
|
||||
constexpr float TURN_MAX{20.f};
|
||||
// metres an area grows by on each side: the vehicles move in the frames since the measurement too
|
||||
constexpr float MARGIN{0.25f};
|
||||
|
||||
auto &state{State};
|
||||
state = fit_state{};
|
||||
if (false == Global.gfx_shadow_sdsm)
|
||||
return false;
|
||||
if ((state.reason = Unavailable) != nullptr)
|
||||
return false;
|
||||
auto const *latest{this->latest()};
|
||||
if (latest == nullptr)
|
||||
{
|
||||
state.reason = "no measurement yet";
|
||||
return false;
|
||||
}
|
||||
auto const &data{latest->data};
|
||||
auto const &source{latest->source};
|
||||
state.age = Frame - latest->frame;
|
||||
state.depthmin = data.depthmin;
|
||||
state.depthmax = data.depthmax;
|
||||
if (state.age > AGE_MAX)
|
||||
{
|
||||
state.reason = "the measurement is too old";
|
||||
return false;
|
||||
}
|
||||
if (data.samples == 0)
|
||||
{
|
||||
state.reason = "only the sky in the view";
|
||||
return false;
|
||||
}
|
||||
if (source.range != fixed_splits().back())
|
||||
{
|
||||
state.reason = "the range of the shadows changed";
|
||||
return false;
|
||||
}
|
||||
// the shadows of this frame are made for its colour pass
|
||||
auto const &camera{Camera};
|
||||
auto const shift{glm::vec3{source.camera - camera.position()}};
|
||||
if (glm::length(shift) > SHIFT_MAX)
|
||||
{
|
||||
state.reason = "the camera jumped";
|
||||
return false;
|
||||
}
|
||||
auto const turnmatrix{glm::mat3{camera.modelview()} * glm::transpose(source.rotation)};
|
||||
auto const turn{std::acos(std::clamp((turnmatrix[0][0] + turnmatrix[1][1] + turnmatrix[2][2] - 1.f) * 0.5f, -1.f, 1.f))};
|
||||
state.turn = glm::degrees(turn);
|
||||
if (state.turn > TURN_MAX)
|
||||
{
|
||||
state.reason = "the view turned too fast";
|
||||
return false;
|
||||
}
|
||||
auto const &projection{camera.projection()};
|
||||
if ((std::abs(projection[0][0] - source.projection[0][0]) > 0.02f * std::abs(projection[0][0])) || (std::abs(projection[1][1] - source.projection[1][1]) > 0.02f * std::abs(projection[1][1])))
|
||||
{
|
||||
state.reason = "the field of view changed";
|
||||
return false;
|
||||
}
|
||||
|
||||
// the first cascade from the camera, like the fixed ones. setup_pass() takes distances up to 1 m as shares of the far
|
||||
// end, so the others start past that
|
||||
Splits[0] = 0.f;
|
||||
for (std::size_t split = 1; split < Splits.size(); ++split)
|
||||
Splits[split] = std::max(data.splits[split], Splits[split - 1] + 1.5f);
|
||||
if (false == std::isfinite(Splits.back()))
|
||||
{
|
||||
state.reason = "invalid measurement";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const inverselight{glm::transpose(source.light)};
|
||||
// the scenery stays where it was while the camera moved, the cab and the train the camera rides with moved along. an area
|
||||
// covers both
|
||||
auto const lightshift{source.light * shift};
|
||||
auto const chord{2.f * std::sin(turn * 0.5f)};
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
// with nothing of the view in a cascade, its map covers the whole slice
|
||||
if (data.counts[cascade] == 0)
|
||||
continue;
|
||||
auto const *bounds{&data.bounds[cascade * 8]};
|
||||
glm::vec3 low{unorder(bounds[0]), unorder(bounds[1]), unorder(bounds[2])};
|
||||
glm::vec3 high{unorder(bounds[4]), unorder(bounds[5]), unorder(bounds[6])};
|
||||
if ((false == std::isfinite(low.x + low.y + high.x + high.y)) || (low.x > high.x) || (low.y > high.y))
|
||||
continue;
|
||||
low = glm::min(low, low + lightshift);
|
||||
high = glm::max(high, high + lightshift);
|
||||
// and what came into the view as it turned, out to the reach of the cascade
|
||||
auto const reach{Splits[cascade + 1] + static_cast<float>(gl::MAX_CASCADES - cascade)};
|
||||
auto const margin{reach * chord + MARGIN + 0.01f * std::max(high.x - low.x, high.y - low.y)};
|
||||
low -= glm::vec3{margin};
|
||||
high += glm::vec3{margin};
|
||||
auto &area{Areas[cascade].emplace()};
|
||||
for (int corner = 0; corner < 8; ++corner)
|
||||
area[corner] = inverselight * glm::vec3{(corner & 1) ? high.x : low.x, (corner & 2) ? high.y : low.y, (corner & 4) ? high.z : low.z};
|
||||
}
|
||||
state.fitted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
float shadow_fit::fit_slice(std::vector<glm::vec4> &Points, std::array<glm::vec3, 8> const *Area, glm::mat3 const &Lightrotation, float &Slicesize)
|
||||
{
|
||||
// a cascade fitted to the depth of the view (gfx.shadow.sdsm) covers the area of the pixels it takes, within the slice of the
|
||||
// view. the areas are compared across the light, the depth along it stays the one of the slice
|
||||
auto quantizationstep{0.f};
|
||||
glm::vec3 slicemin{std::numeric_limits<float>::max()}, slicemax{std::numeric_limits<float>::lowest()};
|
||||
for (auto const &point : Points)
|
||||
{
|
||||
auto const lightpoint{Lightrotation * glm::vec3{point}};
|
||||
slicemin = glm::min(slicemin, lightpoint);
|
||||
slicemax = glm::max(slicemax, lightpoint);
|
||||
}
|
||||
Slicesize = std::max(slicemax.x - slicemin.x, slicemax.y - slicemin.y);
|
||||
if (Area != nullptr)
|
||||
{
|
||||
glm::vec3 areamin{std::numeric_limits<float>::max()}, areamax{std::numeric_limits<float>::lowest()};
|
||||
for (auto const &point : *Area)
|
||||
{
|
||||
auto const lightpoint{Lightrotation * point};
|
||||
areamin = glm::min(areamin, lightpoint);
|
||||
areamax = glm::max(areamax, lightpoint);
|
||||
}
|
||||
auto const fitmin{glm::vec2{glm::max(glm::vec2{slicemin}, glm::vec2{areamin})}};
|
||||
auto const fitmax{glm::vec2{glm::min(glm::vec2{slicemax}, glm::vec2{areamax})}};
|
||||
if ((fitmin.x < fitmax.x) && (fitmin.y < fitmax.y))
|
||||
{
|
||||
// the size changes in steps of a power of two of about 1/16 of it, so that the texels of the map keep their size until
|
||||
// the area grows or shrinks by a step
|
||||
auto const size{std::max(fitmax.x - fitmin.x, fitmax.y - fitmin.y)};
|
||||
quantizationstep = std::exp2(std::ceil(std::log2(std::max(size, 1.f) / 16.f)));
|
||||
Points.clear();
|
||||
auto const inverserotation{glm::transpose(Lightrotation)};
|
||||
for (int corner = 0; corner < 8; ++corner)
|
||||
{
|
||||
auto const lightpoint{glm::vec3{(corner & 1) ? fitmax.x : fitmin.x, (corner & 2) ? fitmax.y : fitmin.y, (corner & 4) ? slicemax.z : slicemin.z}};
|
||||
Points.emplace_back(inverserotation * lightpoint, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return quantizationstep;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
116
rendering/common/shadowfit.h
Normal file
116
rendering/common/shadowfit.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
|
||||
class opengl_camera;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// the cpu side of the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps,
|
||||
// gfx.shadow.sdsm), shared by the renderers (opengl46_shadow_fit, the vulkan renderer). at the end of the colour pass of the main
|
||||
// view two compute passes go over its resolved depth: the nearest and the farthest distance of the pixels which aren't the sky
|
||||
// (shadow_fit_depth.comp), then the distances split between the cascades and, for each cascade, the box of the pixels it takes
|
||||
// in the space of the light (shadow_fit_bounds.comp). the result comes back to the cpu a frame or two later without waiting,
|
||||
// with the camera and the light it was measured with, and the cascades of the frame cover the boxes (fit()) instead of the
|
||||
// whole slices of the view between the fixed distances of shadowtune
|
||||
class shadow_fit
|
||||
{
|
||||
public:
|
||||
// the view and the light a measurement was made with
|
||||
struct view
|
||||
{
|
||||
glm::dvec3 camera; // position of the camera
|
||||
glm::mat4 projection; // projection of the view
|
||||
glm::mat3 rotation; // world to view space, rotation only
|
||||
glm::mat3 light; // world to the space of the light, rotation only
|
||||
float range{0.f}; // the farthest distance the cascades may reach
|
||||
};
|
||||
// a measurement which came back
|
||||
struct result
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
view source;
|
||||
unsigned int frame{0}; // frame of the renderer it was made in
|
||||
};
|
||||
// what the last shadow pass did, for the debug panel
|
||||
struct fit_state
|
||||
{
|
||||
bool fitted{false};
|
||||
char const *reason{nullptr}; // why not, with gfx.shadow.sdsm on
|
||||
unsigned int age{0}; // frames since the measurement
|
||||
float depthmin{0.f};
|
||||
float depthmax{0.f};
|
||||
float turn{0.f}; // of the view since the measurement, degrees
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits{};
|
||||
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
|
||||
std::array<float, gl::MAX_CASCADES> slice{};
|
||||
std::array<float, gl::MAX_CASCADES> drawn{};
|
||||
};
|
||||
using areas = std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES>;
|
||||
|
||||
// a measurement made: its number in the data the passes start from (no pixels, the nearest distance infinite, the bounds
|
||||
// empty), kept with the view until it comes back
|
||||
gl::shadow_fit_data begin_measurement(view const &View, unsigned int const Frame);
|
||||
// the parameters of shadow_fit_bounds.comp: the range, the nearest end of the first split, the least far end of the last
|
||||
// one, the split between the logarithmic and the even splits; and the margins of the bounds
|
||||
static glm::vec4 bounds_params(view const &View);
|
||||
static glm::vec4 margin_params();
|
||||
// a measurement which came back from the gpu
|
||||
void arrived(gl::shadow_fit_data const &Data);
|
||||
// the newest measurement taken in so far
|
||||
result const *latest() const
|
||||
{
|
||||
return m_latest ? &(*m_latest) : nullptr;
|
||||
}
|
||||
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
|
||||
static float unorder(std::uint32_t const Value);
|
||||
|
||||
// the cascades of the frame fitted to the latest measurement: the distances they go from and to, and for each cascade the
|
||||
// corners of the area its map is to cover, camera-relative world space (none: the whole slice of the view). false: the fixed
|
||||
// cascades. Camera: of the colour pass the shadows are made for; Unavailable: why the cascades can't be fitted, or null
|
||||
bool fit(unsigned int const Frame, opengl_camera const &Camera, char const *Unavailable, std::array<float, gl::MAX_CASCADES + 1> &Splits, areas &Areas, fit_state &State) const;
|
||||
|
||||
// the direction the sun shadows are cast in (the sun, its height capped by gfx.shadow.angle.min), and the rotation from the
|
||||
// world to the space of the light of the cascades: x and y across the maps, z along the light
|
||||
static glm::vec3 light_vector(glm::vec3 const &Sun);
|
||||
static glm::mat3 light_rotation(glm::vec3 const &Sun);
|
||||
// the distances the fixed cascades of the sun shadows go from and to (shadowtune)
|
||||
static std::array<float, gl::MAX_CASCADES + 1> fixed_splits();
|
||||
// why the cascades can't be fitted to the depth of a viewport, null if they can. Postprocessing: the viewport has the images
|
||||
// of the post-processing (the resolved depth); Shadow: its view is taken into the shadows; Shadowviews: the viewports which are
|
||||
static char const *unavailable(bool const Postprocessing, bool const Shadow, std::size_t const Shadowviews);
|
||||
// the points of the slice of the view a shadow pass covers (setup_pass()), in its place the corners of the area fitted to the
|
||||
// depth, when there is one (and it lies within the slice); Slicesize: the size of the slice across the light. returns the step
|
||||
// the bounds of the map are quantized to, 0 when the area isn't fitted
|
||||
static float fit_slice(std::vector<glm::vec4> &Points, std::array<glm::vec3, 8> const *Area, glm::mat3 const &Lightrotation, float &Slicesize);
|
||||
|
||||
private:
|
||||
// a measurement on its way back, kept by its number
|
||||
struct pending
|
||||
{
|
||||
std::uint32_t measurement{0}; // 0: none
|
||||
view source;
|
||||
unsigned int frame{0};
|
||||
};
|
||||
static constexpr std::size_t PENDING = 8; // more than the copies the readbacks keep in flight
|
||||
|
||||
std::array<pending, PENDING> m_pending;
|
||||
std::uint32_t m_measurement{0}; // number of the last measurement
|
||||
std::optional<result> m_latest;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
289
rendering/common/skylight.cpp
Normal file
289
rendering/common/skylight.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/skylight.h"
|
||||
|
||||
#include "environment/skydome.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
|
||||
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
|
||||
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
|
||||
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
|
||||
float const SH_Y00{0.282095f};
|
||||
float const SH_Y1{0.488603f};
|
||||
float const PI{3.14159265359f};
|
||||
|
||||
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
|
||||
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
|
||||
{
|
||||
return {SH_Y00,
|
||||
SH_Y1 * n.y,
|
||||
SH_Y1 * n.z,
|
||||
SH_Y1 * n.x,
|
||||
1.092548f * n.x * n.y,
|
||||
1.092548f * n.y * n.z,
|
||||
0.315392f * (3.f * n.z * n.z - 1.f),
|
||||
1.092548f * n.x * n.z,
|
||||
0.546274f * (n.x * n.x - n.y * n.y)};
|
||||
}
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
|
||||
{
|
||||
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
|
||||
}
|
||||
|
||||
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
|
||||
glm::vec3 sky_shoulder(glm::vec3 const &Color)
|
||||
{
|
||||
float const knee{0.25f};
|
||||
float const range{0.95f - knee};
|
||||
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
|
||||
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
|
||||
}
|
||||
|
||||
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
|
||||
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
|
||||
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
|
||||
// renderer, see the notes of the plan, stage S2)
|
||||
float const ATMOSPHERE_SKY_SCALE{16.f};
|
||||
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
|
||||
float const ATMOSPHERE_SUN_SCALE{1.45f};
|
||||
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
|
||||
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
|
||||
float const ATMOSPHERE_FADE_START{-6.f};
|
||||
float const ATMOSPHERE_FADE_END{10.f};
|
||||
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
|
||||
// ground, in the units of the ambient light, is the diffuse colour times their ratio
|
||||
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
|
||||
|
||||
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
|
||||
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
|
||||
float const RAYLEIGH_SCALE_HEIGHT{8.f};
|
||||
float const MIE_SCATTERING{3.996e-3f};
|
||||
float const MIE_EXTINCTION{4.440e-3f};
|
||||
float const MIE_SCALE_HEIGHT{1.2f};
|
||||
float const MIE_ASYMMETRY{0.8f};
|
||||
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
|
||||
float const OZONE_ALTITUDE{25.f};
|
||||
float const OZONE_HALF_WIDTH{15.f};
|
||||
float const BOTTOM_RADIUS{6360.f};
|
||||
float const TOP_RADIUS{6460.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
sky_light::sky_light()
|
||||
{
|
||||
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
|
||||
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
|
||||
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
|
||||
m_atmosphere.top_radius = TOP_RADIUS;
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
|
||||
}
|
||||
|
||||
sky_light::sh_coefficients sky_light::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
|
||||
{
|
||||
sh_coefficients radiance{};
|
||||
auto const &vertices{Skydome.vertices()};
|
||||
auto const &colors{Skydome.colors()};
|
||||
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
|
||||
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
|
||||
float totalweight{0.f};
|
||||
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
|
||||
{
|
||||
auto direction{glm::normalize(vertices[i])};
|
||||
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
|
||||
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
|
||||
// both are undone here, so the light comes from the sky the Perez model describes
|
||||
auto const height{std::max(direction.y, 0.05f)};
|
||||
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
|
||||
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
|
||||
direction.y = std::max(direction.y, 0.f);
|
||||
direction = glm::normalize(direction);
|
||||
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
|
||||
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
|
||||
auto const basis{sh_basis(direction)};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
radiance[c] += color * (basis[c] * weight);
|
||||
totalweight += weight;
|
||||
}
|
||||
if (totalweight <= 0.f)
|
||||
return {};
|
||||
for (auto &coefficient : radiance)
|
||||
coefficient *= 2.f * PI / totalweight;
|
||||
|
||||
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
|
||||
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
|
||||
// which converts the sunlight to them
|
||||
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
|
||||
glm::vec3 skyup{0.f};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
|
||||
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
|
||||
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
|
||||
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
|
||||
radiance[1] += groundradiance * (SH_Y1 * -PI);
|
||||
|
||||
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
|
||||
sh_coefficients directional{};
|
||||
glm::vec3 const average{radiance[0] * SH_Y00};
|
||||
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
|
||||
for (std::size_t c = 1; c < radiance.size(); ++c)
|
||||
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
|
||||
return directional;
|
||||
}
|
||||
|
||||
void sky_light::begin_update()
|
||||
{
|
||||
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
|
||||
m_weight = 0.f;
|
||||
}
|
||||
|
||||
bool sky_light::atmosphere_changed(glm::vec3 const &Sundirection, bool &Medium)
|
||||
{
|
||||
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
|
||||
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
|
||||
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
|
||||
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
|
||||
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
|
||||
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
|
||||
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
|
||||
Medium = mediumchanged;
|
||||
if (!mediumchanged && !sunchanged)
|
||||
return false;
|
||||
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
|
||||
m_tables_medium = medium;
|
||||
m_tables_sun = Sundirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
void sky_light::end_update(basic_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
|
||||
{
|
||||
// the light the simulation set, which is kept where the atmosphere is not used
|
||||
glm::vec3 const oldambient{Sunlight.ambient};
|
||||
glm::vec3 const olddiffuse{Sunlight.diffuse};
|
||||
glm::vec3 const oldfog{Fogcolor};
|
||||
|
||||
if (Global.gfx_atmosphere && m_result)
|
||||
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
|
||||
|
||||
sh_coefficients directional{};
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
// the dome changes with the sun, the weather and the light the simulation works out from them
|
||||
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
|
||||
if (!m_dome_key || *m_dome_key != key)
|
||||
{
|
||||
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
|
||||
m_dome_key = key;
|
||||
}
|
||||
directional = m_dome_sh;
|
||||
}
|
||||
|
||||
m_sunlight = olddiffuse;
|
||||
m_ambient = oldambient;
|
||||
m_fog = oldfog;
|
||||
m_sun_disc_color = glm::vec3{1.f};
|
||||
if (m_weight > 0.f)
|
||||
{
|
||||
auto const &result{*m_result};
|
||||
// the camera's white balance: the sun high in the sky is white
|
||||
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
|
||||
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
|
||||
|
||||
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
|
||||
// with the shadows of the clouds the sun is held back where they are, not everywhere
|
||||
auto const overcastsun{m_cloud_shadows ? 1.f : (1.f - 0.65f * m_overcast)};
|
||||
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * overcastsun};
|
||||
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
|
||||
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
|
||||
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
|
||||
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
|
||||
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
|
||||
|
||||
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
|
||||
m_ambient = glm::mix(oldambient, ambient, m_weight);
|
||||
m_fog = glm::mix(oldfog, fog, m_weight);
|
||||
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
|
||||
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
|
||||
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
auto const evenness{1.f - 0.75f * m_overcast};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
{
|
||||
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
|
||||
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
|
||||
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
|
||||
Fogcolor = m_fog;
|
||||
}
|
||||
|
||||
// directional part of the ambient light for the shading
|
||||
gl::sky_data skydata{};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
|
||||
m_sky_data = skydata;
|
||||
}
|
||||
|
||||
void sky_light::sky_params(glm::vec4 &Param0, glm::vec4 &Param1) const
|
||||
{
|
||||
// the volumetric clouds make the overcast sky themselves
|
||||
Param0 = glm::vec4{m_weight, m_sky_scale, m_clouds ? 0.f : 0.75f * m_overcast, m_sky_flat_luminance};
|
||||
Param1 = glm::vec4{m_white_balance, 0.f};
|
||||
}
|
||||
|
||||
glm::vec3 sky_light::sun_disc_color(glm::vec3 const &Oldcolor) const
|
||||
{
|
||||
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
|
||||
}
|
||||
|
||||
void sky_light::draw_debug_ui() const
|
||||
{
|
||||
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
|
||||
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
|
||||
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
|
||||
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
|
||||
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
|
||||
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
|
||||
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
|
||||
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
|
||||
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
|
||||
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
|
||||
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
|
||||
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
|
||||
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
|
||||
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
131
rendering/common/skylight.h
Normal file
131
rendering/common/skylight.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
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 light of the sky the renderers share (the part of the stage S of claude/gl46-renderer-features-plan.md which isn't the
|
||||
// work of the gpu, taken out of opengl46_sky for the vulkan renderer, stage P1 of claude/vulkan14-renderer-plan.md):
|
||||
// - the directional part of the ambient light, as spherical harmonics (gl::sky_data), worked out from the colours of the sky
|
||||
// dome or from the physical atmosphere
|
||||
// - the parameters of the physical atmosphere (gl::atmosphere_ubs) and when its look-up tables are to be made again
|
||||
// - what the result of the atmosphere read back from the gpu (gl::atmosphere_result) makes of the sunlight, the ambient light
|
||||
// and the fog colour of the frame, the white balance, the share of the atmosphere in the sky and the colour of the sun disc
|
||||
// switched by Global.gfx_ambient_sh and gfx_atmosphere; off, the light the simulation set is left as it is
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "rendering/light.h"
|
||||
|
||||
class CSkyDome;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
class sky_light
|
||||
{
|
||||
public:
|
||||
// the parameters of the atmosphere valid from the start, the tables stay black until they are first made
|
||||
sky_light();
|
||||
|
||||
// the first part of the update of a frame: the overcast of the frame, no atmosphere yet
|
||||
void begin_update();
|
||||
// the inputs of the look-up tables of the atmosphere for the frame (Sundirection normalized): true when the tables are to be
|
||||
// made now, Medium when the transmittance and multiple scattering ones are too (the sky-view one and the ambient light follow
|
||||
// the sun). the parameters are set up for them, and the tables are taken as made
|
||||
bool atmosphere_changed(glm::vec3 const &Sundirection, bool &Medium);
|
||||
// a result of the atmosphere read back from the gpu
|
||||
void set_result(gl::atmosphere_result const &Result)
|
||||
{
|
||||
m_result = Result;
|
||||
}
|
||||
// the rest of the update: replaces the colours of Sunlight (diffuse, ambient) and Fogcolor, which come in as the simulation
|
||||
// set them, with the light of the sky, and works out the directional part of the ambient light. Sundirection and Sunangle
|
||||
// (degrees) are those of the sun, also when the moon is the key light
|
||||
void end_update(basic_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle);
|
||||
|
||||
gl::atmosphere_ubs const &atmosphere() const
|
||||
{
|
||||
return m_atmosphere;
|
||||
}
|
||||
// the directional part of the ambient light for the shading
|
||||
gl::sky_data const &sky_data() const
|
||||
{
|
||||
return m_sky_data;
|
||||
}
|
||||
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
|
||||
float atmosphere_weight() const
|
||||
{
|
||||
return m_weight;
|
||||
}
|
||||
bool has_result() const
|
||||
{
|
||||
return m_result.has_value();
|
||||
}
|
||||
glm::vec3 const &white_balance() const
|
||||
{
|
||||
return m_white_balance;
|
||||
}
|
||||
float sky_scale() const
|
||||
{
|
||||
return m_sky_scale;
|
||||
}
|
||||
float overcast() const
|
||||
{
|
||||
return m_overcast;
|
||||
}
|
||||
// the volumetric clouds are drawn over the sky (it doesn't take the overcast look then), and their shadows hold back the
|
||||
// sunlight (it isn't dimmed by the overcast then)
|
||||
void set_clouds(bool const Clouds, bool const Shadows)
|
||||
{
|
||||
m_clouds = Clouds;
|
||||
m_cloud_shadows = Shadows;
|
||||
}
|
||||
// the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag), param[0] and param[1]
|
||||
void sky_params(glm::vec4 &Param0, glm::vec4 &Param1) const;
|
||||
// colour of the sun disc, the old one blended with the colour the atmosphere gives it
|
||||
glm::vec3 sun_disc_color(glm::vec3 const &Oldcolor) const;
|
||||
// controls of the debug window
|
||||
void draw_debug_ui() const;
|
||||
|
||||
private:
|
||||
// spherical harmonics of order 2, rgb
|
||||
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
|
||||
|
||||
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
|
||||
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
|
||||
|
||||
gl::atmosphere_ubs m_atmosphere{};
|
||||
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
|
||||
// the sky-view table also on the sun
|
||||
std::optional<glm::vec4> m_tables_medium;
|
||||
std::optional<glm::vec3> m_tables_sun;
|
||||
std::optional<gl::atmosphere_result> m_result;
|
||||
gl::sky_data m_sky_data{};
|
||||
|
||||
// what the update made of the frame, for the sky, the sun disc and the debug window
|
||||
float m_weight{0.f};
|
||||
glm::vec3 m_white_balance{1.f};
|
||||
float m_sky_scale{0.f};
|
||||
float m_overcast{0.f};
|
||||
bool m_clouds{false};
|
||||
bool m_cloud_shadows{false};
|
||||
float m_sky_flat_luminance{0.f};
|
||||
glm::vec3 m_sun_disc_color{1.f};
|
||||
glm::vec3 m_sunlight{0.f};
|
||||
glm::vec3 m_ambient{0.f};
|
||||
glm::vec3 m_fog{0.f};
|
||||
|
||||
// cache of the directional ambient light of the dome, remade when its inputs change
|
||||
std::optional<std::array<glm::vec4, 3>> m_dome_key;
|
||||
sh_coefficients m_dome_sh{};
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
373
rendering/common/terrainmaterials.cpp
Normal file
373
rendering/common/terrainmaterials.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/terrainmaterials.h"
|
||||
|
||||
#include "rendering/common/shadersource.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr initial_capacity{8};
|
||||
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
|
||||
float constexpr default_repeat{8.f};
|
||||
|
||||
// array textures: diffuse, normal maps, specular/gloss maps
|
||||
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool terrain_materials::init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
if (false == gpu_init())
|
||||
return false;
|
||||
|
||||
m_white = make_texture("internal_src:terrain/white", texture_kind::white);
|
||||
gpu_white(m_textures.texture(m_white));
|
||||
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
{
|
||||
auto &array{m_arrays[i]};
|
||||
array.handle = make_texture(names[i], texture_kind::layers);
|
||||
array.size = array_sizes[i];
|
||||
array.srgb = (i == 0);
|
||||
gpu_storage(array, initial_capacity, false);
|
||||
}
|
||||
m_capacity = initial_capacity;
|
||||
gpu_changed();
|
||||
|
||||
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
|
||||
try
|
||||
{
|
||||
m_program = GfxRenderer->Fetch_Shader("terrain");
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
texture_handle terrain_materials::make_texture(std::string const &Name, texture_kind const Kind)
|
||||
{
|
||||
auto const handle{m_textures.create(Name, false, GL_RGBA)};
|
||||
auto &texture{m_textures.texture(handle)};
|
||||
gpu_texture(texture, Kind);
|
||||
// the content is supplied here, the texture manager isn't to load, release or replace it
|
||||
texture.is_ready = true;
|
||||
texture.is_static = true;
|
||||
texture.is_rendertarget = true;
|
||||
texture.is_texstub = false;
|
||||
texture.data_state = resource_state::good;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void terrain_materials::read_material(layer &Layer)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
Layer.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
|
||||
Layer.normalmap = (material.shader && material.shader->texture_conf.count("normalmap") > 0);
|
||||
Layer.specgloss = (material.shader && material.shader->texture_conf.count("specgloss") > 0);
|
||||
Layer.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
|
||||
}
|
||||
|
||||
int terrain_materials::layer_index(gfx::terrain_layer const &Layer)
|
||||
{
|
||||
for (auto const &existing : m_layers)
|
||||
if (existing.material == Layer.material && existing.size == Layer.size)
|
||||
return existing.index;
|
||||
|
||||
layer entry;
|
||||
entry.material = Layer.material;
|
||||
entry.size = Layer.size;
|
||||
entry.index = static_cast<int>(m_layers.size());
|
||||
read_material(entry);
|
||||
m_layers.push_back(entry);
|
||||
m_pending = true;
|
||||
|
||||
if (static_cast<int>(m_layers.size()) > m_capacity)
|
||||
{
|
||||
// more room: the arrays are made anew, and every layer copied again
|
||||
auto const capacity{m_capacity * 2};
|
||||
for (auto &array : m_arrays)
|
||||
gpu_storage(array, capacity, true);
|
||||
m_capacity = capacity;
|
||||
for (auto &existing : m_layers)
|
||||
{
|
||||
existing.copied = false;
|
||||
existing.attempts = 0;
|
||||
}
|
||||
gpu_changed();
|
||||
}
|
||||
return entry.index;
|
||||
}
|
||||
|
||||
std::size_t terrain_materials::acquire_slot(material_handle const Reuse)
|
||||
{
|
||||
if (Reuse != null_handle)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Reuse)};
|
||||
if (lookup != m_slotof.end())
|
||||
{
|
||||
m_slots[lookup->second].used = true;
|
||||
return lookup->second;
|
||||
}
|
||||
}
|
||||
if (false == m_free.empty())
|
||||
{
|
||||
auto const index{m_free.back()};
|
||||
m_free.pop_back();
|
||||
m_slots[index].used = true;
|
||||
return index;
|
||||
}
|
||||
auto const index{m_slots.size()};
|
||||
auto const suffix{std::to_string(index)};
|
||||
slot entry;
|
||||
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, texture_kind::weights);
|
||||
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, texture_kind::weights);
|
||||
entry.table = make_texture("internal_src:terrain/table_" + suffix, texture_kind::table);
|
||||
entry.holes = make_texture("internal_src:terrain/holes_" + suffix, texture_kind::holes);
|
||||
// the textures get their content before the material is made of them, which checks they're there
|
||||
gpu_blank(entry);
|
||||
gpu_changed();
|
||||
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
|
||||
std::string const definition{
|
||||
"shader: terrain\n"
|
||||
"texture1: " + name(m_white) + "\n"
|
||||
"texture2: " + name(entry.weights[0]) + "\n"
|
||||
"texture3: " + name(entry.weights[1]) + "\n"
|
||||
"texture4: " + name(m_arrays[0].handle) + "\n"
|
||||
"texture5: " + name(m_arrays[1].handle) + "\n"
|
||||
"texture6: " + name(m_arrays[2].handle) + "\n"
|
||||
"texture7: " + name(entry.table) + "\n"
|
||||
"texture8: " + name(entry.holes) + "\n"};
|
||||
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
|
||||
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't make the material of a chunk");
|
||||
m_state = -1;
|
||||
return static_cast<std::size_t>(-1);
|
||||
}
|
||||
entry.used = true;
|
||||
m_slots.push_back(entry);
|
||||
m_slotof.emplace(entry.material, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
material_handle terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples, std::uint8_t const *Holes)
|
||||
{
|
||||
if (Layers.empty() || false == init())
|
||||
return null_handle;
|
||||
auto const count{std::min(Layers.size(), max_layers)};
|
||||
std::array<int, max_layers> indices{};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (Layers[i].material == null_handle)
|
||||
return null_handle;
|
||||
indices[i] = layer_index(Layers[i]);
|
||||
}
|
||||
auto const slotindex{acquire_slot(Reuse)};
|
||||
if (slotindex == static_cast<std::size_t>(-1))
|
||||
return null_handle;
|
||||
auto &entry{m_slots[slotindex]};
|
||||
|
||||
// weights, four layers to a texture
|
||||
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
|
||||
auto const side{painted ? Samples + 1 : 1};
|
||||
auto const samples{static_cast<std::size_t>(side) * side};
|
||||
std::vector<std::uint8_t> texels(samples * 4);
|
||||
for (std::size_t texture = 0; texture < 2; ++texture)
|
||||
{
|
||||
std::fill(texels.begin(), texels.end(), 0);
|
||||
for (std::size_t channel = 0; channel < 4; ++channel)
|
||||
{
|
||||
auto const layer{texture * 4 + channel};
|
||||
if (layer >= count)
|
||||
break;
|
||||
if (false == painted)
|
||||
{
|
||||
if (layer == 0)
|
||||
texels[channel] = 255;
|
||||
continue;
|
||||
}
|
||||
auto const *source{Weights + layer * samples};
|
||||
for (std::size_t i = 0; i < samples; ++i)
|
||||
texels[i * 4 + channel] = source[i];
|
||||
}
|
||||
gpu_weights(entry.weights[texture], side, texels.data());
|
||||
}
|
||||
entry.count = count;
|
||||
entry.layers = indices;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
entry.rotations[i] = Layers[i].rotation;
|
||||
write_table(entry);
|
||||
|
||||
// the holes: the cells cut out entirely are left out of the mesh, the program of the holes cuts the rest of the openings
|
||||
auto const cut{Holes != nullptr && HoleSamples > 0};
|
||||
{
|
||||
auto const holeside{cut ? HoleSamples + 1 : 1};
|
||||
std::uint8_t const none{0};
|
||||
gpu_holes(entry.holes, holeside, cut ? Holes : &none);
|
||||
gpu_changed();
|
||||
}
|
||||
auto &material{m_materials.material(entry.material)};
|
||||
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
|
||||
if (m_holesprogram == nullptr && cut)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_holesprogram = GfxRenderer->Fetch_Shader("terrain_holes");
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the shader of the holes: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
// the program of the holes discards fragments, which the depth prepass of the plain one never does
|
||||
material.shader = (cut && m_holesprogram != nullptr) ? m_holesprogram : m_program;
|
||||
return entry.material;
|
||||
}
|
||||
|
||||
void terrain_materials::write_table(slot const &Slot)
|
||||
{
|
||||
// what the layers are: index in the arrays, repeat, which maps they have, reflection
|
||||
std::array<float, max_layers * 2 * 4> table{};
|
||||
for (std::size_t i = 0; i < Slot.count; ++i)
|
||||
{
|
||||
auto const &layer{m_layers[Slot.layers[i]]};
|
||||
table[i * 4 + 0] = static_cast<float>(layer.index);
|
||||
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
|
||||
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
|
||||
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
|
||||
table[(max_layers + i) * 4 + 0] = layer.reflection;
|
||||
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
|
||||
auto const angle{glm::radians(static_cast<double>(Slot.rotations[i]))};
|
||||
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
|
||||
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
|
||||
}
|
||||
gpu_table(Slot.table, table.data());
|
||||
gpu_changed();
|
||||
}
|
||||
|
||||
void terrain_materials::on_environment_change()
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return;
|
||||
bool changed{false};
|
||||
for (auto &layer : m_layers)
|
||||
{
|
||||
auto const &material{m_materials.material(layer.material)};
|
||||
if (false == (material.update_on_season_change || material.update_on_weather_change))
|
||||
continue;
|
||||
// the reloaded material can have other textures, other maps and another size
|
||||
read_material(layer);
|
||||
layer.copied = false;
|
||||
layer.attempts = 0;
|
||||
changed = true;
|
||||
}
|
||||
if (false == changed)
|
||||
return;
|
||||
m_pending = true;
|
||||
// the tables of the chunks hold what the layers have, freed slots get theirs anew when they're used again
|
||||
for (auto const &entry : m_slots)
|
||||
if (entry.used)
|
||||
write_table(entry);
|
||||
}
|
||||
|
||||
void terrain_materials::release(material_handle const Material)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Material)};
|
||||
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
|
||||
return;
|
||||
m_slots[lookup->second].used = false;
|
||||
m_free.push_back(lookup->second);
|
||||
}
|
||||
|
||||
bool terrain_materials::copy_layer(layer &Layer)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
auto const slot = [&](char const *Name, int const Fallback) {
|
||||
if (material.shader)
|
||||
{
|
||||
auto const lookup{material.shader->texture_conf.find(Name)};
|
||||
if (lookup != material.shader->texture_conf.end())
|
||||
return static_cast<int>(lookup->second.id);
|
||||
return -1;
|
||||
}
|
||||
return Fallback;
|
||||
};
|
||||
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
|
||||
// all maps of the layer have to be ready, so they go together
|
||||
std::array<texture_source *, 3> sources{};
|
||||
for (std::size_t i = 0; i < slots.size(); ++i)
|
||||
{
|
||||
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
|
||||
continue;
|
||||
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
|
||||
if (false == texture.is_ready)
|
||||
{
|
||||
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
|
||||
// one released by the sweep or never read has to be read first
|
||||
if (texture.data_state == resource_state::none)
|
||||
{
|
||||
texture.reload_on_use = false;
|
||||
texture.load();
|
||||
}
|
||||
if (texture.data_state == resource_state::failed)
|
||||
continue;
|
||||
if (false == texture.create())
|
||||
{
|
||||
if (++Layer.attempts < copy_attempts)
|
||||
return false;
|
||||
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
|
||||
continue; // waited long enough, the layer goes without it
|
||||
}
|
||||
}
|
||||
sources[i] = &texture;
|
||||
}
|
||||
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
|
||||
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
gpu_copy(m_arrays[i], Layer.index, sources[i], defaults[i]);
|
||||
Layer.copied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void terrain_materials::update()
|
||||
{
|
||||
if (false == m_pending || m_state <= 0)
|
||||
return;
|
||||
|
||||
gpu_begin_copies();
|
||||
bool copied{false};
|
||||
m_pending = false;
|
||||
for (auto &layer : m_layers)
|
||||
{
|
||||
if (layer.copied)
|
||||
continue;
|
||||
if (copy_layer(layer))
|
||||
copied = true;
|
||||
else
|
||||
m_pending = true;
|
||||
}
|
||||
gpu_end_copies(copied);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
142
rendering/common/terrainmaterials.h
Normal file
142
rendering/common/terrainmaterials.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "model/material.h"
|
||||
#include "model/Texture.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
// materials of the heightmap terrain (shader mat_terrain, mat_terrain_holes for chunks with holes), whichever api draws them.
|
||||
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
|
||||
// of its own, with textures holding the weights of its layers and a table of what its layers are.
|
||||
// what the layers and the chunks are is kept here; the textures are made and filled by the renderer (the gpu_ functions)
|
||||
class terrain_materials
|
||||
{
|
||||
public:
|
||||
terrain_materials(material_manager &Materials, texture_bank &Textures) : m_materials(Materials), m_textures(Textures) {}
|
||||
virtual ~terrain_materials() = default;
|
||||
|
||||
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
|
||||
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr);
|
||||
void release(material_handle const Material);
|
||||
// copies the textures of new layers into the arrays; to be called before the frame is drawn
|
||||
void update();
|
||||
// the layer materials which change with the season or the weather were reloaded, their textures are copied again
|
||||
void on_environment_change();
|
||||
|
||||
protected:
|
||||
static constexpr std::size_t max_layers{8};
|
||||
|
||||
struct layer
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
float size{0.f}; // as given by the chunk
|
||||
float repeat{8.f}; // metres the textures are repeated at
|
||||
int index{0}; // in the arrays
|
||||
bool normalmap{false};
|
||||
bool specgloss{false};
|
||||
float reflection{0.f};
|
||||
bool copied{false};
|
||||
int attempts{0};
|
||||
};
|
||||
struct texture_array
|
||||
{
|
||||
texture_handle handle{null_handle};
|
||||
int size{0};
|
||||
bool srgb{false};
|
||||
};
|
||||
struct slot
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
std::array<texture_handle, 2> weights{null_handle, null_handle};
|
||||
texture_handle table{null_handle};
|
||||
texture_handle holes{null_handle}; // where the ground is cut, R8; a single texel of 0 without holes
|
||||
bool used{false};
|
||||
// what the table was made of, kept to make it again when a layer changes
|
||||
std::size_t count{0};
|
||||
std::array<int, max_layers> layers{};
|
||||
std::array<float, max_layers> rotations{};
|
||||
};
|
||||
// what a texture made here holds
|
||||
enum class texture_kind
|
||||
{
|
||||
white, // a single white texel, rgba8
|
||||
layers, // an array of the layer maps, rgba8 (srgb for the diffuse)
|
||||
weights, // weights of four layers, rgba8 with mipmaps, clamped
|
||||
table, // the table of the layers of a chunk, max_layers x 2 rgba32f, nearest
|
||||
holes // the mask of the holes, r8, led between the samples, clamped
|
||||
};
|
||||
|
||||
// the textures of the arrays: diffuse, normal maps, specular/gloss
|
||||
std::array<texture_array, 3> const &arrays() const
|
||||
{
|
||||
return m_arrays;
|
||||
}
|
||||
|
||||
// the objects the textures are drawn with (the copy of the layer maps); false: the terrain can't be drawn
|
||||
virtual bool gpu_init() = 0;
|
||||
// the object of a texture made by the bank, of the kind
|
||||
virtual void gpu_texture(texture_source &Texture, texture_kind const Kind) = 0;
|
||||
// the white texel
|
||||
virtual void gpu_white(texture_source &Texture) = 0;
|
||||
// room for Layers layers in the array; Fresh: the array had layers before, they go
|
||||
virtual void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) = 0;
|
||||
// the content of the textures of a new slot, before its material is made of them: no weights, a table of zeros, no holes
|
||||
virtual void gpu_blank(slot const &Slot) = 0;
|
||||
// Side x Side texels of rgba8, the mipmaps made of them
|
||||
virtual void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) = 0;
|
||||
// max_layers x 2 texels of rgba32f
|
||||
virtual void gpu_table(texture_handle const Texture, float const *Table) = 0;
|
||||
// Side x Side samples of r8
|
||||
virtual void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) = 0;
|
||||
// the copies of the layer maps into the arrays: begun, then a copy per array of each layer copied, ended; Copied: whether any
|
||||
// was made (the mipmaps of the arrays are made then)
|
||||
virtual void gpu_begin_copies() = 0;
|
||||
// Source: the map of the layer, or null when it has none, the array is filled with Default then
|
||||
virtual void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) = 0;
|
||||
virtual void gpu_end_copies(bool const Copied) = 0;
|
||||
// textures were made or given new content (the opengl renderer forgets the textures bound)
|
||||
virtual void gpu_changed() {}
|
||||
|
||||
material_manager &m_materials;
|
||||
texture_bank &m_textures;
|
||||
|
||||
private:
|
||||
bool init();
|
||||
texture_handle make_texture(std::string const &Name, texture_kind const Kind);
|
||||
void read_material(layer &Layer);
|
||||
int layer_index(gfx::terrain_layer const &Layer);
|
||||
void write_table(slot const &Slot);
|
||||
bool copy_layer(layer &Layer);
|
||||
std::size_t acquire_slot(material_handle const Reuse);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
texture_handle m_white{null_handle};
|
||||
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
|
||||
int m_capacity{0}; // layers the arrays have room for
|
||||
std::vector<layer> m_layers;
|
||||
bool m_pending{false};
|
||||
std::vector<slot> m_slots;
|
||||
std::vector<std::size_t> m_free;
|
||||
std::unordered_map<material_handle, std::size_t> m_slotof;
|
||||
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
|
||||
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
343
rendering/common/weatherstate.cpp
Normal file
343
rendering/common/weatherstate.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/weatherstate.h"
|
||||
|
||||
#include "environment/starcatalog.h"
|
||||
#include "environment/weather.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "widgets/weatherpanel.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// how many times the cell of a genus fits in the tile of the shape noise, and its spread in the tile of the coverage noise
|
||||
double const SHAPE_TILE_CELLS{4.0};
|
||||
double const COVERAGE_TILE_SPREADS{4.0};
|
||||
// size of the patches of the ground fog
|
||||
float const FOG_PATCH_SIZE{300.f};
|
||||
// the haze the atmosphere draws in the sky already, as an extinction: about the haze of a day with 30 km of visibility
|
||||
float const SKY_HAZE_EXTINCTION{3.f / 30000.f};
|
||||
// near end of the fog volume (view depth, metres)
|
||||
float const FOG_VOLUME_NEAR{0.5f};
|
||||
// bands the panorama is drawn in, one a frame
|
||||
int const PANORAMA_BANDS{8};
|
||||
// brightness of a star of magnitude 0 in the units of the scene
|
||||
float const STAR_BRIGHTNESS{4.f};
|
||||
// stars dimmer than this aren't drawn
|
||||
float const STAR_LIMIT{6.5f};
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
// the position in a tiling noise, of a period in metres, kept small for the single precision of the shaders
|
||||
double wrap(double const Value, double const Period)
|
||||
{
|
||||
return Value - Period * std::floor(Value / Period);
|
||||
}
|
||||
|
||||
// the colour of a star of a colour temperature, linear, of luminance 1, somewhat paler than the black body: the eye sees most
|
||||
// stars white
|
||||
glm::vec3 star_color(float const Temperature)
|
||||
{
|
||||
auto const t{std::clamp(Temperature, 1500.f, 40000.f) / 100.f};
|
||||
glm::vec3 color;
|
||||
color.r = (t <= 66.f) ? 255.f : 329.698727446f * std::pow(t - 60.f, -0.1332047592f);
|
||||
color.g = (t <= 66.f) ? 99.4708025861f * std::log(t) - 161.1195681661f : 288.1221695283f * std::pow(t - 60.f, -0.0755148492f);
|
||||
color.b = (t >= 66.f) ? 255.f : (t <= 19.f) ? 0.f : 138.5177312231f * std::log(t - 10.f) - 305.0447927307f;
|
||||
color = glm::pow(glm::clamp(color / 255.f, glm::vec3{0.f}, glm::vec3{1.f}), glm::vec3{2.2f});
|
||||
color /= std::max(luminance(color), 1e-3f);
|
||||
return glm::mix(color, glm::vec3{1.f}, 0.5f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
weather_state::quality_level weather_state::quality(int const Clouds, int const Fog)
|
||||
{
|
||||
quality_level level;
|
||||
switch (std::clamp(Clouds, 1, 4))
|
||||
{
|
||||
case 1: level = {4, 32, 3, false, {512, 128}, {}}; break;
|
||||
case 2: level = {2, 48, 4, true, {1024, 256}, {}}; break;
|
||||
case 3: level = {2, 80, 6, true, {1024, 256}, {}}; break;
|
||||
default: level = {2, 128, 8, true, {2048, 512}, {}}; break;
|
||||
}
|
||||
switch (std::clamp(Fog, 1, 4))
|
||||
{
|
||||
case 1: level.fog_grid = {80, 45, 48}; break;
|
||||
case 2: level.fog_grid = {160, 90, 64}; break;
|
||||
case 3: level.fog_grid = {160, 90, 96}; break;
|
||||
default: level.fog_grid = {240, 135, 128}; break;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
weather_state::weather_state()
|
||||
{
|
||||
// a grey fog and no clouds until the first update
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{0.5f, 0.5f, 0.5f, 1.f};
|
||||
m_weather.fog_haze = glm::vec4{3.f / 10000.f, 1.f / 800.f, SKY_HAZE_EXTINCTION, 1.f};
|
||||
m_weather.fog_light = glm::vec4{0.f, 1.f, 0.6f, 1.f / 5000.f};
|
||||
}
|
||||
|
||||
void weather_state::update(float const Atmosphereweight, bool const Skyview, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera)
|
||||
{
|
||||
auto const &weather{simulation::Environment.weather()};
|
||||
auto const &conditions{weather.conditions()};
|
||||
m_fog_volume_valid = false;
|
||||
m_clouds_view_valid = false;
|
||||
++m_frame;
|
||||
|
||||
auto const height{static_cast<float>(Camera.y) - weather.ground()};
|
||||
auto const time{weather.time()};
|
||||
auto const sun{glm::normalize(Keylight)};
|
||||
|
||||
// the fog. the visibility is where a dark object fades to 5%: the extinction is 3 over it
|
||||
auto const haze{3.f / conditions.visibility};
|
||||
auto const hazerate{1.f / conditions.haze_height};
|
||||
m_weather.fog_haze = glm::vec4{haze, hazerate, SKY_HAZE_EXTINCTION, 1.f};
|
||||
if (conditions.groundfog_thickness > 0.f)
|
||||
{
|
||||
auto const thickness{conditions.groundfog_thickness};
|
||||
m_weather.fog_ground = glm::vec4{std::max(3.f / conditions.groundfog_visibility - haze, 0.f), 1.f / (0.25f * thickness), 0.75f * thickness, conditions.fog_noise};
|
||||
}
|
||||
else
|
||||
m_weather.fog_ground = glm::vec4{0.f, 1.f, 0.f, conditions.fog_noise};
|
||||
// the patches drift with the wind, slower than the clouds
|
||||
auto const drift{glm::dvec2{weather.cloud_offset()} * 0.3};
|
||||
auto const patch{static_cast<double>(FOG_PATCH_SIZE)};
|
||||
m_weather.fog_noise = glm::vec4{static_cast<float>(wrap(Camera.x - drift.x, patch)), static_cast<float>(wrap(Camera.y, patch)), static_cast<float>(wrap(Camera.z - drift.y, patch)), 1.f / FOG_PATCH_SIZE};
|
||||
// the extinction at the height of the camera tells how far things are hidden
|
||||
auto const extinction{haze * std::exp(-hazerate * std::max(height, 0.f)) +
|
||||
m_weather.fog_ground.x * std::exp(-m_weather.fog_ground.y * std::max(height - m_weather.fog_ground.z, 0.f))};
|
||||
// 2% left at 1.3 visibilities; the draw range is twice this
|
||||
m_fog_range = std::clamp(3.f / std::max(extinction, 1e-7f) / 1.5f, 5.f, 1e6f);
|
||||
|
||||
// the share of the light of the fog the sun gives, which the shadows hold back
|
||||
auto const sunlight{luminance(Keycolor) * 1.5f * std::max(sun.y, 0.f)};
|
||||
auto const skylight{luminance(Ambient) * 0.65f};
|
||||
m_sun_share = (sunlight + skylight > 1e-6f) ? sunlight / (sunlight + skylight) : 0.f;
|
||||
|
||||
// clouds
|
||||
auto const layers{std::min(conditions.clouds.size(), gl::MAX_CLOUD_LAYERS)};
|
||||
m_clouds_drawn = (Global.gfx_clouds > 0) && (layers > 0);
|
||||
m_average_transmittance = 1.f;
|
||||
auto const velocity{weather.cloud_velocity()};
|
||||
auto const speed{glm::length(velocity)};
|
||||
auto const wind{speed > 1e-3f ? glm::dvec2{velocity / speed} : glm::dvec2{1.0, 0.0}};
|
||||
m_weather.cloud_wind = glm::vec4{static_cast<float>(wind.x), static_cast<float>(wind.y), speed, m_cloud_detail_scale};
|
||||
for (std::size_t i = 0; i < gl::MAX_CLOUD_LAYERS; ++i)
|
||||
{
|
||||
auto &target{m_weather.cloud_layers[i]};
|
||||
if (i >= layers)
|
||||
{
|
||||
target = gl::cloud_layer_ubs{};
|
||||
continue;
|
||||
}
|
||||
auto const &layer{conditions.clouds[i]};
|
||||
auto const &genus{cloud_genus_info(layer.type)};
|
||||
target.heights = glm::vec4{layer.base, layer.top, 1.f / std::max(layer.top - layer.base, 1.f), static_cast<float>(layer.type)};
|
||||
auto const shapetile{static_cast<double>(genus.cell) * SHAPE_TILE_CELLS};
|
||||
auto const coveragetile{static_cast<double>(genus.spread) * COVERAGE_TILE_SPREADS};
|
||||
target.shape = glm::vec4{layer.coverage, genus.extinction * layer.density, static_cast<float>(1.0 / shapetile), genus.erosion};
|
||||
target.profile = glm::vec4{genus.bottom, genus.top, genus.top_variation, genus.anvil};
|
||||
// the higher, the faster the wind carries them
|
||||
auto const offset{glm::dvec2{weather.cloud_offset()} * (1.0 + static_cast<double>(layer.base) / 8000.0)};
|
||||
// the camera's place in the noise, in the space stretched along the wind as the shaders take it
|
||||
auto const place{glm::dvec2{Camera.x, Camera.z} - offset};
|
||||
auto const along{glm::dot(place, wind)};
|
||||
auto const stretched{(place - wind * along) + wind * (along / std::max(static_cast<double>(genus.stretch), 1.0))};
|
||||
target.motion = glm::vec4{static_cast<float>(wrap(stretched.x, shapetile)), static_cast<float>(wrap(stretched.y, shapetile)), static_cast<float>(1.0 / coveragetile), genus.stretch};
|
||||
// the clouds change their shapes slowly; the seed moves the layers apart in the noise
|
||||
auto const evolution{std::fmod(static_cast<double>(conditions.seed) * 0.6180339887 + static_cast<double>(i) * 0.37 + time * 0.00002, 1.0)};
|
||||
target.extra = glm::vec4{genus.flat ? 1.f : 0.f, static_cast<float>(evolution), static_cast<float>(wrap(stretched.x, coveragetile)), static_cast<float>(wrap(stretched.y, coveragetile))};
|
||||
m_average_transmittance *= 1.f - std::clamp(layer.coverage * genus.opacity * std::min(layer.density, 1.5f), 0.f, 1.f);
|
||||
}
|
||||
if (Global.gfx_clouds <= 0)
|
||||
m_average_transmittance = 1.f;
|
||||
m_weather.fog_light = glm::vec4{m_sun_share, m_average_transmittance, 0.6f, fog_density()};
|
||||
m_weather.weather_camera = glm::vec4{height, static_cast<float>(std::fmod(time, 100000.0)), m_clouds_drawn ? 1.f : 0.f, m_clouds_drawn ? static_cast<float>(layers) : 0.f};
|
||||
m_weather.weather_sun_direction = glm::vec4{sun, sun.y > -0.02f ? 1.f : 0.f};
|
||||
m_weather.weather_sun_color = glm::vec4{Keycolor, m_cloud_sun_scale};
|
||||
m_weather.weather_ambient = glm::vec4{Ambient * 1.3f, m_cloud_sky_scale};
|
||||
m_weather.fog_volume_params = glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target = glm::vec4{1.f, 1.f, Global.gfx_fog_range, 1.f - Atmosphereweight};
|
||||
|
||||
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
|
||||
m_horizon_on_gpu = (Atmosphereweight > 0.f) && Skyview;
|
||||
if (false == m_horizon_on_gpu)
|
||||
{
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{Fogcolor, 1.f};
|
||||
}
|
||||
|
||||
// the shadows of the clouds
|
||||
m_shadows_wanted = m_clouds_drawn && Global.gfx_clouds_shadows && (sun.y > 0.02f);
|
||||
m_weather.cloud_shadow_params = glm::vec4{0.f};
|
||||
m_report = to_metar(conditions);
|
||||
}
|
||||
|
||||
void weather_state::place_shadows(int const Size, glm::dvec3 const &Camera)
|
||||
{
|
||||
// a map snapped to its texels so it doesn't crawl as the camera moves
|
||||
auto const extent{static_cast<double>(Global.gfx_clouds_shadows_range)};
|
||||
auto const texel{extent / static_cast<double>(Size)};
|
||||
auto const centre{glm::dvec2{std::round(Camera.x / texel) * texel - Camera.x, std::round(Camera.z / texel) * texel - Camera.z}};
|
||||
m_weather.cloud_shadow_params = glm::vec4{static_cast<float>(centre.x), static_cast<float>(centre.y), static_cast<float>(1.0 / extent), 0.f};
|
||||
}
|
||||
|
||||
void weather_state::shadows_drawn(bool const Drawn)
|
||||
{
|
||||
m_shadows_valid = Drawn;
|
||||
if (Drawn)
|
||||
m_weather.cloud_shadow_params.w = 1.f;
|
||||
}
|
||||
|
||||
void weather_state::panorama_resized()
|
||||
{
|
||||
m_panorama_full = false;
|
||||
m_panorama_band = 0;
|
||||
}
|
||||
|
||||
void weather_state::panorama_band(glm::ivec2 const &Size, int &First, int &Rows)
|
||||
{
|
||||
auto const bandrows{(Size.y + PANORAMA_BANDS - 1) / PANORAMA_BANDS};
|
||||
First = m_panorama_full ? m_panorama_band * bandrows : 0;
|
||||
Rows = m_panorama_full ? bandrows : Size.y;
|
||||
m_panorama_band = m_panorama_full ? (m_panorama_band + 1) % PANORAMA_BANDS : 0;
|
||||
m_panorama_full = true;
|
||||
}
|
||||
|
||||
bool weather_state::continuity(view_state const &Previous, glm::dvec3 const &Camera)
|
||||
{
|
||||
// a jump of the camera (another vehicle, the free camera) leaves nothing to blend with
|
||||
return Previous.valid && glm::length(Previous.camera - Camera) < 50.0;
|
||||
}
|
||||
|
||||
glm::vec3 weather_state::fog_jitter(bool const Temporal) const
|
||||
{
|
||||
// the cells are sampled at another point in every frame: along the depth in the Halton sequence of 2, across the view in
|
||||
// those of 3 and 5 (fog_inject.comp moves the point of every cell by a value of its own)
|
||||
auto const halton = [](int Index, int const Base) {
|
||||
float value{0.f}, fraction{1.f};
|
||||
for (; Index > 0; Index /= Base)
|
||||
{
|
||||
fraction /= static_cast<float>(Base);
|
||||
value += fraction * static_cast<float>(Index % Base);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
auto const sample{(m_frame % 64) + 1};
|
||||
return Temporal ? glm::vec3{halton(sample, 3), halton(sample, 5), halton(sample, 2)} : glm::vec3{0.5f};
|
||||
}
|
||||
|
||||
glm::vec4 weather_state::fog_slicing(int const Slices, glm::vec3 const &Jitter)
|
||||
{
|
||||
auto const farthest{std::max(Global.gfx_fog_range, FOG_VOLUME_NEAR * 4.f)};
|
||||
return glm::vec4{FOG_VOLUME_NEAR, farthest, static_cast<float>(Slices) / std::log((farthest + gl::FOG_VOLUME_BIAS) / (FOG_VOLUME_NEAR + gl::FOG_VOLUME_BIAS)), Jitter.z};
|
||||
}
|
||||
|
||||
void weather_state::fog_made(glm::vec4 const &Slicing, glm::ivec3 const &Grid)
|
||||
{
|
||||
m_fog_slicing = Slicing;
|
||||
m_fog_grid = Grid;
|
||||
m_fog_volume_valid = true;
|
||||
}
|
||||
|
||||
void weather_state::setup_pass(bool const Volume, glm::ivec2 const &Size)
|
||||
{
|
||||
auto const volume{Volume && m_fog_volume_valid && (Global.gfx_fog == 1)};
|
||||
m_weather.fog_volume_params = volume ? glm::vec4{1.f, m_fog_slicing.x, m_fog_slicing.z, static_cast<float>(m_fog_grid.z)} : glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target.x = 1.f / static_cast<float>(std::max(Size.x, 1));
|
||||
m_weather.fog_volume_target.y = 1.f / static_cast<float>(std::max(Size.y, 1));
|
||||
m_weather.fog_volume_target.z = volume ? m_fog_slicing.y : Global.gfx_fog_range;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> weather_state::star_vertices()
|
||||
{
|
||||
// the stars, in the equatorial frame
|
||||
std::vector<glm::vec4> stars;
|
||||
auto const *catalogue{star_catalogue::stars()};
|
||||
for (std::size_t i = 0; i < star_catalogue::size(); ++i)
|
||||
{
|
||||
auto const &star{catalogue[i]};
|
||||
auto const magnitude{static_cast<float>(star.magnitude) * 0.01f};
|
||||
if (magnitude > STAR_LIMIT)
|
||||
continue;
|
||||
auto const ascension{glm::radians(static_cast<float>(star.right_ascension) * 0.01f)};
|
||||
auto const declination{glm::radians(static_cast<float>(star.declination) * 0.01f)};
|
||||
stars.emplace_back(std::cos(declination) * std::cos(ascension), std::cos(declination) * std::sin(ascension), std::sin(declination), magnitude);
|
||||
// a phase of the twinkling which doesn't repeat from star to star
|
||||
auto const phase{std::fmod(static_cast<float>(i) * 0.6180339887f, 1.f)};
|
||||
stars.emplace_back(star_color(static_cast<float>(star.temperature) * 10.f), phase);
|
||||
}
|
||||
return stars;
|
||||
}
|
||||
|
||||
bool weather_state::stars(float const Sunangle, double const Siderealtime, double const Latitude, glm::mat4 const &View, glm::mat4 &Toworld, glm::mat4 &Toview, glm::vec4 &Params) const
|
||||
{
|
||||
if (!Global.gfx_stars)
|
||||
return false;
|
||||
// the stars come out between the civil and the nautical twilight
|
||||
auto const night{1.f - glm::smoothstep(-12.f, -3.f, Sunangle)};
|
||||
if (night <= 0.f)
|
||||
return false;
|
||||
// the equatorial frame (z to the pole, x to the vernal equinox) to the sky of the observer (x west, y up, z north)
|
||||
auto const theta{glm::radians(Siderealtime)};
|
||||
auto const phi{glm::radians(Latitude)};
|
||||
auto const st{static_cast<float>(std::sin(theta))}, ct{static_cast<float>(std::cos(theta))};
|
||||
auto const sp{static_cast<float>(std::sin(phi))}, cp{static_cast<float>(std::cos(phi))};
|
||||
glm::mat3 equatorial;
|
||||
equatorial[0] = glm::vec3{st, cp * ct, -sp * ct};
|
||||
equatorial[1] = glm::vec3{-ct, cp * st, -sp * st};
|
||||
equatorial[2] = glm::vec3{0.f, sp, cp};
|
||||
Toworld = glm::mat4{equatorial};
|
||||
Toview = glm::mat4{glm::mat3{View}};
|
||||
Params = glm::vec4{STAR_BRIGHTNESS * Global.gfx_stars_brightness * night, static_cast<float>(std::fmod(Timer::GetTime(), 10000.0)), 2.5f, 0.f};
|
||||
return true;
|
||||
}
|
||||
|
||||
void weather_state::draw_debug_ui()
|
||||
{
|
||||
char const *fogmodes[]{"analytic (along the ray, as the old fog)", "volumetric"};
|
||||
ImGui::Combo("fog (gfx.fog)", &Global.gfx_fog, fogmodes, IM_ARRAYSIZE(fogmodes));
|
||||
char const *levels[]{"low", "medium", "high", "ultra"};
|
||||
auto fogquality{std::clamp(Global.gfx_fog_quality, 1, 4) - 1};
|
||||
if (ImGui::Combo("fog volume quality (gfx.fog.quality)", &fogquality, levels, IM_ARRAYSIZE(levels)))
|
||||
Global.gfx_fog_quality = fogquality + 1;
|
||||
ImGui::SliderFloat("fog volume depth, m (gfx.fog.range)", &Global.gfx_fog_range, 100.f, 10000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("lamps in the fog (gfx.fog.lights)", &Global.gfx_fog_lights, 0.f, 4.f);
|
||||
ImGui::Checkbox("shadows of the sun and headlights in the fog (gfx.fog.shadows)", &Global.gfx_fog_shadows);
|
||||
ImGui::Checkbox("fog blended over frames (gfx.fog.temporal)", &Global.gfx_fog_temporal);
|
||||
ImGui::SliderFloat("share of a new frame in the fog (gfx.fog.temporal.share)", &Global.gfx_fog_temporal_share, 0.02f, 1.f);
|
||||
char const *cloudlevels[]{"none", "low", "medium", "high", "ultra"};
|
||||
ImGui::Combo("clouds (gfx.clouds)", &Global.gfx_clouds, cloudlevels, IM_ARRAYSIZE(cloudlevels));
|
||||
ImGui::SliderFloat("cloud distance, m (gfx.clouds.distance)", &Global.gfx_clouds_distance, 5000.f, 200000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::Checkbox("cloud shadows (gfx.clouds.shadows)", &Global.gfx_clouds_shadows);
|
||||
ImGui::SliderFloat("cloud shadow map, m (gfx.clouds.shadows.range)", &Global.gfx_clouds_shadows_range, 1000.f, 100000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("cloud sunlight (tuning, not saved)", &m_cloud_sun_scale, 0.25f, 3.f);
|
||||
ImGui::SliderFloat("cloud sky light (tuning, not saved)", &m_cloud_sky_scale, 0.f, 3.f);
|
||||
ImGui::SliderFloat("cloud edge detail (tuning, not saved)", &m_cloud_detail_scale, 0.f, 3.f);
|
||||
ImGui::Checkbox("stars (gfx.stars)", &Global.gfx_stars);
|
||||
ImGui::SliderFloat("star brightness", &Global.gfx_stars_brightness, 0.f, 8.f);
|
||||
ImGui::Text("fog range %.0f m, sun share of the fog light %.2f, clouds let through %.2f on average", m_fog_range, m_sun_share, m_average_transmittance);
|
||||
ImGui::Separator();
|
||||
ui::weather_controls(simulation::Environment.weather());
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
168
rendering/common/weatherstate.h
Normal file
168
rendering/common/weatherstate.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
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 weather the renderers draw (the part of the stage F of claude/gl46-renderer-features-plan.md which isn't the work of the
|
||||
// gpu, taken out of opengl46_weather for the vulkan renderer, stage P2 of claude/vulkan14-renderer-plan.md): the block of the
|
||||
// weather of the shaders (gl::weather_ubs) made from the weather of the simulation (environment/weather.h) for the frame and for
|
||||
// each pass, the quality levels, the place of the map of the shadows of the clouds, the bands of the panorama of the clouds, the
|
||||
// sampling of the fog volume over the frames, and the stars of the catalogue (environment/starcatalog.h)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
class weather_state
|
||||
{
|
||||
public:
|
||||
struct quality_level
|
||||
{
|
||||
int divisor; // of the resolution of the clouds of the main view
|
||||
int steps; // samples through a layer at most
|
||||
int light_steps; // samples towards the sun
|
||||
bool detail; // with the small noise
|
||||
glm::ivec2 panorama; // size of the panorama
|
||||
glm::ivec3 fog_grid; // cells of the fog volume
|
||||
};
|
||||
static quality_level quality(int const Clouds, int const Fog);
|
||||
|
||||
// the camera of the previous frame of the main view, for the blending over frames
|
||||
struct view_state
|
||||
{
|
||||
glm::mat4 projection_view{1.f}; // world axes to clip space, camera at the origin
|
||||
glm::dvec3 camera{0.0};
|
||||
glm::vec4 slicing{0.f};
|
||||
bool valid{false};
|
||||
};
|
||||
|
||||
// a grey fog and no clouds until the first update
|
||||
weather_state();
|
||||
|
||||
// what the weather of the simulation is for the frame: the block, whether the clouds are drawn and cast shadows. before the
|
||||
// gpu work of the frame. Atmosphereweight: share of the atmosphere in the sky, Skyview: its sky-view table is there to
|
||||
// give the colours of the horizon; Keylight: world direction towards the sun or the moon, Keycolor: its colour as the
|
||||
// shading gets it, Ambient: the ambient light of the sky, Fogcolor: the old colour of the fog
|
||||
void update(float const Atmosphereweight, bool const Skyview, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera);
|
||||
// the shadows of the clouds are to be drawn in this frame (after update())
|
||||
bool shadows_wanted() const
|
||||
{
|
||||
return m_shadows_wanted;
|
||||
}
|
||||
// the place of the map of the shadows of Size texels, snapped to its texels so it doesn't crawl as the camera moves
|
||||
void place_shadows(int const Size, glm::dvec3 const &Camera);
|
||||
// the map of the shadows was drawn (or not) in this frame
|
||||
void shadows_drawn(bool const Drawn);
|
||||
// the rows of the panorama of the clouds of Size to draw in this frame: a band of it, all of it the first time after
|
||||
// panorama_resized()
|
||||
void panorama_band(glm::ivec2 const &Size, int &First, int &Rows);
|
||||
void panorama_resized();
|
||||
// a camera close enough to the one of the previous frame to blend with
|
||||
static bool continuity(view_state const &Previous, glm::dvec3 const &Camera);
|
||||
// the point the cells of the fog volume are sampled at in this frame (Temporal: it moves from frame to frame) and the slicing of
|
||||
// the volume of Slices slices it makes
|
||||
glm::vec3 fog_jitter(bool const Temporal) const;
|
||||
static glm::vec4 fog_slicing(int const Slices, glm::vec3 const &Jitter);
|
||||
// the clouds of the main view and the fog volume were made in this frame
|
||||
void clouds_made()
|
||||
{
|
||||
m_clouds_view_valid = true;
|
||||
}
|
||||
void fog_made(glm::vec4 const &Slicing, glm::ivec3 const &Grid);
|
||||
// the fog of a pass: the volume of the main view (made in the same frame), or along the ray. Size: the target of the pass
|
||||
void setup_pass(bool const Volume, glm::ivec2 const &Size);
|
||||
|
||||
gl::weather_ubs const &block() const
|
||||
{
|
||||
return m_weather;
|
||||
}
|
||||
// the colours of the horizon come from the sky-view table of the atmosphere on the gpu, not from the cpu
|
||||
bool horizon_on_gpu() const
|
||||
{
|
||||
return m_horizon_on_gpu;
|
||||
}
|
||||
// true when the volumetric clouds are drawn (the sky leaves its own overcast look then)
|
||||
bool clouds_drawn() const
|
||||
{
|
||||
return m_clouds_drawn;
|
||||
}
|
||||
bool clouds_view_valid() const
|
||||
{
|
||||
return m_clouds_view_valid;
|
||||
}
|
||||
// true when the clouds cast shadows (the sunlight isn't dimmed by the overcast then, the shadows do it)
|
||||
bool cloud_shadows() const
|
||||
{
|
||||
return m_shadows_valid;
|
||||
}
|
||||
bool fog_volume_valid() const
|
||||
{
|
||||
return m_fog_volume_valid;
|
||||
}
|
||||
// fog density of a regular draw (model_ubs.fog_density): 1 / the fog range
|
||||
float fog_density() const
|
||||
{
|
||||
return 1.f / m_fog_range;
|
||||
}
|
||||
// distance the fog hides things at, for the draw range: a dark object fades to about 2% there
|
||||
float fog_range() const
|
||||
{
|
||||
return m_fog_range;
|
||||
}
|
||||
// frames updated, the sequence of the sampling over frames
|
||||
int frame() const
|
||||
{
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
// the stars, in the equatorial frame: for each star the direction and the magnitude, then the colour and the phase of its
|
||||
// twinkling
|
||||
static std::vector<glm::vec4> star_vertices();
|
||||
// whether the stars are drawn in the sky pass, and what with: the rotation from the equatorial frame to the world, from the
|
||||
// world to the view of the pass (View: rotation of its camera), the brightness, the time, the size of the points
|
||||
bool stars(float const Sunangle, double const Siderealtime, double const Latitude, glm::mat4 const &View, glm::mat4 &Toworld, glm::mat4 &Toview, glm::vec4 &Params) const;
|
||||
|
||||
// controls of the debug window, Volumeavailable: the renderer has the fog volume
|
||||
void draw_debug_ui();
|
||||
|
||||
// the cameras of the clouds and of the fog volume of the previous frame
|
||||
view_state previous_clouds;
|
||||
view_state previous_fog;
|
||||
|
||||
private:
|
||||
gl::weather_ubs m_weather{};
|
||||
bool m_horizon_on_gpu{false};
|
||||
bool m_shadows_wanted{false};
|
||||
bool m_shadows_valid{false};
|
||||
bool m_clouds_drawn{false};
|
||||
bool m_clouds_view_valid{false};
|
||||
int m_panorama_band{0};
|
||||
bool m_panorama_full{false};
|
||||
glm::ivec3 m_fog_grid{0};
|
||||
bool m_fog_volume_valid{false};
|
||||
float m_fog_range{5000.f};
|
||||
glm::vec4 m_fog_slicing{0.f};
|
||||
int m_frame{0};
|
||||
|
||||
// tuning of the look of the clouds in the debug window, not saved: scales of their sunlight, of their sky light and of the
|
||||
// erosion of their edges by the detail noise (weather_sun_color.w, weather_ambient.w, cloud_wind.w)
|
||||
float m_cloud_sun_scale{1.f};
|
||||
float m_cloud_sky_scale{1.f};
|
||||
float m_cloud_detail_scale{1.f};
|
||||
// what the debug window shows
|
||||
float m_sun_share{0.f};
|
||||
float m_average_transmittance{1.f};
|
||||
std::string m_report;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
#include "rendering/common/exposure.h"
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
@@ -17,16 +18,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
|
||||
float const HISTOGRAM_LOG_MIN{-12.f};
|
||||
float const HISTOGRAM_LOG_MAX{6.f};
|
||||
// the bloom chain goes down this many levels at most, and not below this size
|
||||
std::size_t const BLOOM_LEVELS{6};
|
||||
int const BLOOM_MIN_SIZE{8};
|
||||
// format of the levels: no alpha, no negative values, half the memory of rgba16f
|
||||
GLint const BLOOM_FORMAT{GL_R11F_G11F_B10F};
|
||||
// soft knee of the threshold, a fraction of it
|
||||
float const BLOOM_KNEE{0.5f};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -58,12 +51,8 @@ void opengl46_exposure::bind()
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> opengl46_exposure::make_bloom_chain(int const Width, int const Height)
|
||||
{
|
||||
auto chain{std::make_unique<bloom_chain>()};
|
||||
glm::ivec2 size{Width, Height};
|
||||
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
|
||||
for (auto const &size : gfx::exposure_settings::bloom_sizes(Width, Height))
|
||||
{
|
||||
size = glm::max(size / 2, glm::ivec2{1});
|
||||
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
|
||||
break;
|
||||
chain->sizes.push_back(size);
|
||||
auto &texture{chain->down.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
@@ -94,19 +83,18 @@ void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
|
||||
m_exposure_timer.begin();
|
||||
m_exposure_ssbo->bind_base();
|
||||
Image.bind(0);
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
glProgramUniform2f(*m_histogram_program, 0, HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN));
|
||||
auto const histogramrange{gfx::exposure_settings::histogram_range()};
|
||||
glProgramUniform2f(*m_histogram_program, 0, histogramrange.x, histogramrange.y);
|
||||
auto const groupsize{2 * static_cast<int>(gl::EXPOSURE_HISTOGRAM_GROUP_SIZE)};
|
||||
m_histogram_program->dispatch((Image.get_width() + groupsize - 1) / groupsize, (Image.get_height() + groupsize - 1) / groupsize);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
glProgramUniform2f(*m_average_program, 0, HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins);
|
||||
glProgramUniform4f(*m_average_program, 1,
|
||||
static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)),
|
||||
Global.gfx_postfx_autoexposure_speed_dark,
|
||||
Global.gfx_postfx_autoexposure_speed_bright,
|
||||
Global.gfx_postfx_autoexposure_key);
|
||||
glProgramUniform2f(*m_average_program, 2, std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max));
|
||||
auto const averagerange{gfx::exposure_settings::average_range()};
|
||||
auto const adaptation{gfx::exposure_settings::adaptation(Deltatime)};
|
||||
auto const exposurerange{gfx::exposure_settings::exposure_range()};
|
||||
glProgramUniform2f(*m_average_program, 0, averagerange.x, averagerange.y);
|
||||
glProgramUniform4f(*m_average_program, 1, adaptation.x, adaptation.y, adaptation.z, adaptation.w);
|
||||
glProgramUniform2f(*m_average_program, 2, exposurerange.x, exposurerange.y);
|
||||
m_average_program->dispatch(1);
|
||||
// read by the tonemapping, and copied for the debug window
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
@@ -122,7 +110,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
|
||||
// down, the first step from the image itself
|
||||
for (std::size_t level = 0; level < Chain.sizes.size(); ++level)
|
||||
{
|
||||
Model.param[0] = glm::vec4{(level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
|
||||
Model.param[0] = gfx::exposure_settings::bloom_params(level);
|
||||
Modelubo.update(Model);
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_down->apply(level == 0 ? Image : *Chain.down[level - 1], Chain.down_fb[level].get());
|
||||
@@ -141,12 +129,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
|
||||
|
||||
glm::vec4 opengl46_exposure::tonemap_params(std::size_t const Bloomlevels) const
|
||||
{
|
||||
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
|
||||
return glm::vec4{
|
||||
bloom,
|
||||
Global.gfx_postfx_autoexposure ? 1.f : 0.f,
|
||||
std::exp2(Global.gfx_postfx_autoexposure_compensation),
|
||||
Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
|
||||
return gfx::exposure_settings::tonemap_params(Bloomlevels);
|
||||
}
|
||||
|
||||
std::string opengl46_exposure::info_times()
|
||||
@@ -161,15 +144,5 @@ std::string opengl46_exposure::info_times()
|
||||
|
||||
void opengl46_exposure::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
|
||||
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
|
||||
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
|
||||
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
|
||||
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", m_readback_data[2], m_readback_data[0], m_readback_data[1], std::log2(std::max(m_readback_data[1], 1e-6f)));
|
||||
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
|
||||
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
|
||||
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
|
||||
gfx::exposure_settings::draw_debug_ui(m_readback_data);
|
||||
}
|
||||
|
||||
@@ -10,46 +10,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46gpuparticles.h"
|
||||
|
||||
#include "rendering/particles.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "gl/vao.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
|
||||
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
|
||||
// the origin of the smoke moves by whole steps when the camera gets this far from it
|
||||
double constexpr ORIGIN_STEP{256.0};
|
||||
double constexpr ORIGIN_REACH{1024.0};
|
||||
// how much the smoke scatters the light onwards (Henyey-Greenstein)
|
||||
float constexpr SMOKE_FORWARD{0.3f};
|
||||
|
||||
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
|
||||
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
|
||||
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
|
||||
|
||||
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
|
||||
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
|
||||
struct precipitation_kind
|
||||
{
|
||||
float fall;
|
||||
float exposure;
|
||||
float opacity;
|
||||
float forward;
|
||||
float brightness;
|
||||
};
|
||||
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
|
||||
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
|
||||
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
|
||||
double constexpr WIND_SHARE{0.5};
|
||||
// the camera faster than this from a frame to the next was moved, not driven or flown
|
||||
double constexpr CAMERA_JUMP{120.0};
|
||||
|
||||
std::unique_ptr<gl::program> make_program(std::vector<std::string> const &Names)
|
||||
{
|
||||
std::vector<std::unique_ptr<gl::shader>> shaders;
|
||||
@@ -76,11 +43,8 @@ opengl46_gpu_particles::~opengl46_gpu_particles()
|
||||
::glDeleteVertexArrays(1, &m_vao);
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::init()
|
||||
bool opengl46_gpu_particles::gpu_init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
try
|
||||
{
|
||||
m_smokesimulate = make_program({"particles_smoke_simulate.comp"});
|
||||
@@ -109,88 +73,26 @@ bool opengl46_gpu_particles::init()
|
||||
m_precipitationcommand = std::make_unique<gl::ssbo>(4 * sizeof(GLuint), gl::PARTICLE_COMMAND_SSBO_BINDING);
|
||||
m_smokereadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_precipitationreadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::smoke_available()
|
||||
void opengl46_gpu_particles::gpu_clear()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::precipitation_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::clear()
|
||||
{
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_poolfull = false;
|
||||
m_sourcerecords.clear();
|
||||
m_places = 0;
|
||||
m_particles.reset();
|
||||
m_sources.reset();
|
||||
m_owners.reset();
|
||||
m_smokeoutput.reset();
|
||||
m_precipitationoutput.reset();
|
||||
m_precipitationcapacity = 0;
|
||||
m_originset = false;
|
||||
m_lastupdate = 0;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::layout(particle_manager &Particles)
|
||||
void opengl46_gpu_particles::gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources)
|
||||
{
|
||||
auto const &sources{Particles.sequence()};
|
||||
// the sources already laid out which asked for the same places since
|
||||
std::size_t kept{0};
|
||||
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
|
||||
++kept;
|
||||
if ((kept == m_requested.size()) && (kept == sources.size()) && m_particles)
|
||||
return;
|
||||
if (kept < m_requested.size())
|
||||
{
|
||||
// a source went away or changed: all anew
|
||||
kept = 0;
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_places = 0;
|
||||
m_poolfull = false;
|
||||
}
|
||||
auto const oldplaces{m_places};
|
||||
|
||||
auto first{m_places};
|
||||
m_pool.resize(sources.size());
|
||||
m_requested.resize(sources.size());
|
||||
for (std::size_t i = kept; i < sources.size(); ++i)
|
||||
{
|
||||
m_requested[i] = sources[i].max_particles();
|
||||
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
|
||||
m_pool[i] = pool_source{};
|
||||
if (first + size > MAX_SMOKE_PLACES)
|
||||
{
|
||||
if (false == m_poolfull)
|
||||
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
|
||||
m_poolfull = true;
|
||||
continue;
|
||||
}
|
||||
m_pool[i].first = first;
|
||||
m_pool[i].size = size;
|
||||
first += size;
|
||||
}
|
||||
m_places = first;
|
||||
|
||||
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
|
||||
for (std::size_t i = 0; i < m_pool.size(); ++i)
|
||||
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
|
||||
|
||||
auto const places{static_cast<GLsizeiptr>(std::max<std::uint32_t>(m_places, 1))};
|
||||
auto const places{static_cast<GLsizeiptr>(Places)};
|
||||
// the particles of the places kept go over to the new storage, the new places are dead
|
||||
auto particles{std::make_unique<gl::ssbo>(places * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)), gl::PARTICLE_SSBO_BINDING)};
|
||||
::glClearNamedBufferData(*particles, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
if (m_particles && (oldplaces > 0) && (kept > 0))
|
||||
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(oldplaces) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
|
||||
if (m_particles && (Kept > 0))
|
||||
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(Kept) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
|
||||
m_particles = std::move(particles);
|
||||
|
||||
auto const ensure = [](std::unique_ptr<gl::ssbo> &Buffer, GLsizeiptr const Size, GLuint const Binding) {
|
||||
@@ -201,96 +103,29 @@ void opengl46_gpu_particles::layout(particle_manager &Particles)
|
||||
};
|
||||
ensure(m_owners, places * static_cast<GLsizeiptr>(sizeof(std::uint32_t)), gl::PARTICLE_OWNER_SSBO_BINDING);
|
||||
ensure(m_smokeoutput, places * static_cast<GLsizeiptr>(sizeof(gl::particle_draw)), gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(sources.size(), 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
|
||||
m_owners->update(owners.data(), 0, static_cast<GLsizeiptr>(owners.size() * sizeof(std::uint32_t)));
|
||||
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
|
||||
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(Sources, 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
|
||||
m_owners->update(Owners.data(), 0, static_cast<GLsizeiptr>(Owners.size() * sizeof(std::uint32_t)));
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
|
||||
void opengl46_gpu_particles::gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records)
|
||||
{
|
||||
if (false == init())
|
||||
return;
|
||||
layout(Particles);
|
||||
if (Particles.updates() == m_lastupdate)
|
||||
return;
|
||||
m_lastupdate = Particles.updates();
|
||||
if (m_places == 0)
|
||||
return;
|
||||
|
||||
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
|
||||
glm::vec3 shift{0.f};
|
||||
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
|
||||
if (false == m_originset)
|
||||
{
|
||||
m_origin = snapped;
|
||||
m_originset = true;
|
||||
}
|
||||
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
|
||||
{
|
||||
shift = glm::vec3{m_origin - snapped};
|
||||
m_origin = snapped;
|
||||
}
|
||||
|
||||
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
|
||||
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
|
||||
auto const &sources{Particles.sequence()};
|
||||
m_smokealive = 0;
|
||||
for (std::size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
auto const &state{sources[i].gpu()};
|
||||
auto &pool{m_pool[i]};
|
||||
auto &record{m_sourcerecords[i]};
|
||||
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
|
||||
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
|
||||
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
|
||||
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
|
||||
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
|
||||
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
|
||||
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], state.spread};
|
||||
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
|
||||
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
|
||||
record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, state.fadein};
|
||||
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
if (pool.size > 0)
|
||||
{
|
||||
pool.head = (pool.head + spawns) % pool.size;
|
||||
if (spawns > 0)
|
||||
m_smokealive += pool.size;
|
||||
}
|
||||
}
|
||||
if (false == m_sourcerecords.empty())
|
||||
m_sources->update(m_sourcerecords.data(), 0, static_cast<GLsizeiptr>(m_sourcerecords.size() * sizeof(gl::particle_smoke_source)));
|
||||
if (false == Records.empty())
|
||||
m_sources->update(Records.data(), 0, static_cast<GLsizeiptr>(Records.size() * sizeof(gl::particle_smoke_source)));
|
||||
|
||||
m_simulatetimer.begin();
|
||||
m_particles->bind_base();
|
||||
m_sources->bind_base();
|
||||
m_owners->bind_base();
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto &program{*m_smokesimulate};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform4f(program, 1, static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z);
|
||||
::glProgramUniform4f(program, 2, wind.x, wind.y, wind.z, 0.f);
|
||||
::glProgramUniform1ui(program, 3, static_cast<GLuint>(m_lastupdate));
|
||||
program.dispatch(groups(m_places));
|
||||
::glProgramUniform1ui(program, 0, Simulation.places);
|
||||
::glProgramUniform4fv(program, 1, 1, glm::value_ptr(Simulation.step));
|
||||
::glProgramUniform4fv(program, 2, 1, glm::value_ptr(Simulation.wind));
|
||||
::glProgramUniform1ui(program, 3, static_cast<GLuint>(Simulation.seed));
|
||||
program.dispatch(groups(Simulation.places));
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_simulatetimer.end();
|
||||
}
|
||||
|
||||
std::array<glm::vec4, 5> opengl46_gpu_particles::planes(view const &View)
|
||||
{
|
||||
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
|
||||
auto const matrix{glm::transpose(View.projection * View.modelview)};
|
||||
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
|
||||
for (auto &plane : result)
|
||||
{
|
||||
auto const length{glm::length(glm::vec3{plane})};
|
||||
if (length > 0.f)
|
||||
plane /= length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::collect(gl::readback &Readback, std::size_t &Drawn)
|
||||
{
|
||||
GLuint command[4];
|
||||
@@ -317,11 +152,9 @@ void opengl46_gpu_particles::draw(GLuint const Output, GLuint const Command, gl:
|
||||
::glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_smoke(view const &View)
|
||||
void opengl46_gpu_particles::gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn)
|
||||
{
|
||||
if ((false == init()) || (m_places == 0))
|
||||
return 0;
|
||||
collect(*m_smokereadback, m_smokedrawn);
|
||||
collect(*m_smokereadback, Drawn);
|
||||
|
||||
m_smoketimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
@@ -332,125 +165,60 @@ std::size_t opengl46_gpu_particles::render_smoke(view const &View)
|
||||
m_smokeoutput->bind_base();
|
||||
m_smokecommand->bind_base();
|
||||
auto &program{*m_smokeprepare};
|
||||
auto const origin{glm::vec3{m_origin - View.camera}};
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform3f(program, 1, origin.x, origin.y, origin.z);
|
||||
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 3, Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f);
|
||||
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(m_places));
|
||||
::glProgramUniform1ui(program, 0, Draw.places);
|
||||
::glProgramUniform3fv(program, 1, 1, glm::value_ptr(Draw.origin));
|
||||
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(Draw.view));
|
||||
::glProgramUniform4fv(program, 3, 1, glm::value_ptr(Draw.options));
|
||||
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(Draw.planes[0]));
|
||||
program.dispatch(groups(Draw.places));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
::glProgramUniform4f(*m_smokedraw, 0, View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4fv(*m_smokedraw, 0, 1, glm::value_ptr(Draw.soft));
|
||||
draw(*m_smokeoutput, *m_smokecommand, *m_smokedraw);
|
||||
m_smoketimer.end();
|
||||
|
||||
m_smokereadback->push(*m_smokecommand);
|
||||
return m_smokedrawn;
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
|
||||
void opengl46_gpu_particles::gpu_collect_precipitation(std::size_t &Drawn)
|
||||
{
|
||||
if (false == init())
|
||||
return 0;
|
||||
collect(*m_precipitationreadback, m_precipitationdrawn);
|
||||
collect(*m_precipitationreadback, Drawn);
|
||||
}
|
||||
|
||||
auto const &kind{Snow ? SNOW : RAIN};
|
||||
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
|
||||
if ((false == m_framestarted) || (Frame != m_frame))
|
||||
{
|
||||
m_framestarted = true;
|
||||
m_frame = Frame;
|
||||
auto const seconds{Timer::GetDeltaTime()};
|
||||
if (seconds > 0.0)
|
||||
{
|
||||
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
|
||||
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
|
||||
}
|
||||
m_time = std::fmod(m_time + seconds, 3600.0);
|
||||
if (m_camerastarted)
|
||||
{
|
||||
auto const velocity{(View.camera - m_lastcamera) / seconds};
|
||||
if (glm::length(velocity) < CAMERA_JUMP)
|
||||
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
|
||||
}
|
||||
}
|
||||
m_lastcamera = View.camera;
|
||||
m_camerastarted = true;
|
||||
}
|
||||
|
||||
// the particles, more of them in heavier rain or snow
|
||||
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
|
||||
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
|
||||
m_precipitationcount = count;
|
||||
m_precipitationsnow = Snow;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (m_precipitationcapacity < count)
|
||||
{
|
||||
m_precipitationcapacity = std::max(count, most);
|
||||
auto const size{static_cast<GLsizeiptr>(m_precipitationcapacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
|
||||
if (!m_precipitationoutput)
|
||||
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
else
|
||||
m_precipitationoutput->resize(size);
|
||||
}
|
||||
void opengl46_gpu_particles::gpu_precipitation_storage(std::uint32_t const Capacity)
|
||||
{
|
||||
auto const size{static_cast<GLsizeiptr>(Capacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
|
||||
if (!m_precipitationoutput)
|
||||
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
else
|
||||
m_precipitationoutput->resize(size);
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::gpu_render_precipitation(precipitation_draw const &Draw)
|
||||
{
|
||||
m_precipitationtimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
m_precipitationcommand->update(command, 0, sizeof(command));
|
||||
m_precipitationoutput->bind_base();
|
||||
m_precipitationcommand->bind_base();
|
||||
auto &program{*m_precipitationprepare};
|
||||
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
|
||||
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
|
||||
::glProgramUniform4ui(program, 0, second, third, count, Snow ? 1u : 0u);
|
||||
std::array<glm::vec4, 3> offsets;
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
|
||||
}
|
||||
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(offsets[0]));
|
||||
::glProgramUniform4f(program, 4, BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f);
|
||||
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
|
||||
::glProgramUniform4f(program, 5, velocity.x, velocity.y, velocity.z, kind.fall);
|
||||
::glProgramUniform4f(program, 6, static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity));
|
||||
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 11, 1.f, kind.forward, kind.brightness, 0.f);
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(count));
|
||||
::glProgramUniform4uiv(program, 0, 1, glm::value_ptr(Draw.counts));
|
||||
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(Draw.offsets[0]));
|
||||
::glProgramUniform4fv(program, 4, 1, glm::value_ptr(Draw.sizes));
|
||||
::glProgramUniform4fv(program, 5, 1, glm::value_ptr(Draw.velocity));
|
||||
::glProgramUniform4fv(program, 6, 1, glm::value_ptr(Draw.timing));
|
||||
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(Draw.view));
|
||||
::glProgramUniform4fv(program, 11, 1, glm::value_ptr(Draw.options));
|
||||
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(Draw.planes[0]));
|
||||
program.dispatch(groups(Draw.count));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
// the width of a pixel a metre in front of the camera, for the least width of a streak
|
||||
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
|
||||
::glProgramUniform4f(*m_precipitationdraw, 0, View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4f(*m_precipitationdraw, 1, pixel, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4fv(*m_precipitationdraw, 0, 1, glm::value_ptr(Draw.soft));
|
||||
::glProgramUniform4fv(*m_precipitationdraw, 1, 1, glm::value_ptr(Draw.pixel));
|
||||
draw(*m_precipitationoutput, *m_precipitationcommand, *m_precipitationdraw);
|
||||
m_precipitationtimer.end();
|
||||
|
||||
m_precipitationreadback->push(*m_precipitationcommand);
|
||||
return m_precipitationdrawn;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info() const
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return {};
|
||||
std::string text;
|
||||
if (m_places > 0)
|
||||
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
|
||||
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
|
||||
if (m_precipitationcount > 0)
|
||||
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
|
||||
+ " drawn\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info_times()
|
||||
@@ -460,13 +228,13 @@ std::string opengl46_gpu_particles::info_times()
|
||||
if (auto const time = Timer.result())
|
||||
text += std::string(text.empty() ? " particles:" : ",") + " " + Name + " " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
|
||||
};
|
||||
if (m_places > 0)
|
||||
if (places() > 0)
|
||||
{
|
||||
add(m_simulatetimer, "smoke simulation");
|
||||
add(m_smoketimer, "smoke");
|
||||
}
|
||||
if (m_precipitationcount > 0)
|
||||
add(m_precipitationtimer, m_precipitationsnow ? "snow" : "rain");
|
||||
if (precipitation_count() > 0)
|
||||
add(m_precipitationtimer, precipitation_snow() ? "snow" : "rain");
|
||||
if (false == text.empty())
|
||||
text += "\n";
|
||||
return text;
|
||||
|
||||
@@ -9,87 +9,42 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "rendering/common/gpuparticles.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
class particle_manager;
|
||||
|
||||
// particles of the gpu for the opengl 4.6 renderer: the smoke and the rain or snow.
|
||||
//
|
||||
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
|
||||
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
|
||||
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
|
||||
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
|
||||
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
|
||||
//
|
||||
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
|
||||
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
|
||||
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
|
||||
//
|
||||
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
|
||||
// their precision far from the centre of the scenery
|
||||
class opengl46_gpu_particles
|
||||
// particles of the gpu for the opengl 4.6 renderer (gfx::gpu_particles): the storage buffers, the compute programs and the
|
||||
// indirect draws
|
||||
class opengl46_gpu_particles : public gfx::gpu_particles
|
||||
{
|
||||
public:
|
||||
using view = gfx::gpu_particles::view;
|
||||
|
||||
opengl46_gpu_particles() = default;
|
||||
~opengl46_gpu_particles();
|
||||
opengl46_gpu_particles(opengl46_gpu_particles const &) = delete;
|
||||
opengl46_gpu_particles &operator=(opengl46_gpu_particles const &) = delete;
|
||||
~opengl46_gpu_particles() override;
|
||||
|
||||
// what a colour pass draws with
|
||||
struct view
|
||||
{
|
||||
glm::mat4 modelview; // world axes around the camera to the view space of the pass
|
||||
glm::mat4 projection;
|
||||
glm::dvec3 camera;
|
||||
int height{0}; // of the target, pixels
|
||||
bool soft{false}; // the resolved depth of the scene drawn so far is bound to gl::SCENE_DEPTH_TEX
|
||||
};
|
||||
|
||||
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
|
||||
bool smoke_available();
|
||||
bool precipitation_available();
|
||||
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
|
||||
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
|
||||
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
|
||||
std::size_t render_smoke(view const &View);
|
||||
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
|
||||
// returns: particles drawn, read back
|
||||
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
|
||||
// drops what is on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
|
||||
std::string info() const;
|
||||
std::string info_times();
|
||||
|
||||
private:
|
||||
struct pool_source
|
||||
{
|
||||
std::uint32_t first{0};
|
||||
std::uint32_t size{0};
|
||||
std::uint32_t head{0};
|
||||
};
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) override;
|
||||
void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) override;
|
||||
void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) override;
|
||||
void gpu_collect_precipitation(std::size_t &Drawn) override;
|
||||
void gpu_precipitation_storage(std::uint32_t const Capacity) override;
|
||||
void gpu_render_precipitation(precipitation_draw const &Draw) override;
|
||||
void gpu_clear() override;
|
||||
|
||||
bool init();
|
||||
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
|
||||
// the particles stay; otherwise all is laid out anew
|
||||
void layout(particle_manager &Particles);
|
||||
private:
|
||||
// reads back the instances counted by a pass; Drawn is left as it was when nothing came back yet
|
||||
static void collect(gl::readback &Readback, std::size_t &Drawn);
|
||||
// the planes of the view of the pass, for the culling of the compute passes
|
||||
static std::array<glm::vec4, 5> planes(view const &View);
|
||||
void draw(GLuint Output, GLuint Command, gl::program &Program);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
std::unique_ptr<gl::program> m_smokesimulate;
|
||||
std::unique_ptr<gl::program> m_smokeprepare;
|
||||
std::unique_ptr<gl::program> m_smokedraw;
|
||||
@@ -98,22 +53,12 @@ class opengl46_gpu_particles
|
||||
GLuint m_vao{0}; // the attributes of the instances, from the buffer of the draw bound to it before the draw
|
||||
|
||||
// smoke
|
||||
std::vector<pool_source> m_pool;
|
||||
std::vector<std::size_t> m_requested; // the sizes the sources asked for
|
||||
std::vector<gl::particle_smoke_source> m_sourcerecords;
|
||||
std::uint32_t m_places{0};
|
||||
bool m_poolfull{false}; // some sources got no places
|
||||
std::unique_ptr<gl::ssbo> m_particles;
|
||||
std::unique_ptr<gl::ssbo> m_sources;
|
||||
std::unique_ptr<gl::ssbo> m_owners;
|
||||
std::unique_ptr<gl::ssbo> m_smokeoutput;
|
||||
std::unique_ptr<gl::ssbo> m_smokecommand;
|
||||
std::unique_ptr<gl::readback> m_smokereadback;
|
||||
std::uint64_t m_lastupdate{0};
|
||||
glm::dvec3 m_origin{0.0};
|
||||
bool m_originset{false};
|
||||
std::size_t m_smokedrawn{0};
|
||||
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
|
||||
gl::gpu_timer m_simulatetimer;
|
||||
gl::gpu_timer m_smoketimer;
|
||||
|
||||
@@ -121,17 +66,5 @@ class opengl46_gpu_particles
|
||||
std::unique_ptr<gl::ssbo> m_precipitationoutput;
|
||||
std::unique_ptr<gl::ssbo> m_precipitationcommand;
|
||||
std::unique_ptr<gl::readback> m_precipitationreadback;
|
||||
std::uint32_t m_precipitationcapacity{0};
|
||||
unsigned int m_frame{0};
|
||||
bool m_framestarted{false};
|
||||
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
|
||||
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
|
||||
double m_time{0.0}; // seconds, for the flutter of the flakes
|
||||
glm::dvec3 m_lastcamera{0.0};
|
||||
glm::dvec3 m_cameravelocity{0.0};
|
||||
bool m_camerastarted{false};
|
||||
std::size_t m_precipitationdrawn{0};
|
||||
std::uint32_t m_precipitationcount{0};
|
||||
bool m_precipitationsnow{false};
|
||||
gl::gpu_timer m_precipitationtimer;
|
||||
};
|
||||
|
||||
@@ -12,51 +12,12 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "rendering/openglcamera.h"
|
||||
#include "model/Texture.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr max_blades{65536}; // drawn in a tile at most
|
||||
float constexpr blade_reach{1.5f}; // metres the bounds of a tile reach above its ground, for the blades
|
||||
|
||||
// the share of the blades drawn at specified distance: fewer further away, for speed (from Thinning), and fewer and fewer from
|
||||
// Fade to Reach, where the grass ends; the same as lod_share() of grass.vert
|
||||
float lod_share(float const Distance, float const Thinning, float const Fade, float const Reach)
|
||||
{
|
||||
auto const share{Distance <= Thinning ? 1.f : std::pow(Thinning / Distance, 1.5f)};
|
||||
auto const t{std::clamp((Distance - Fade) / std::max(Reach - Fade, 1e-3f), 0.f, 1.f)};
|
||||
return share * (1.f - t * t * (3.f - 2.f * t));
|
||||
}
|
||||
|
||||
// the grass through the year, by the day of the year: x how dry it is, y how fresh (the green of spring), z how grown (share
|
||||
// of the height). the turns follow the seasons of the textures (world_environment::compute_season(): winter till day 65,
|
||||
// spring till 158, summer till 252, autumn till 341): dormant and dry in winter, greening up and growing through spring,
|
||||
// fresh in May, drying out from August, the dry grass lying lower towards winter
|
||||
glm::vec3 grass_season(int const Yearday)
|
||||
{
|
||||
struct point
|
||||
{
|
||||
float day;
|
||||
glm::vec3 value;
|
||||
};
|
||||
static point const year[]{{1.f, {0.80f, 0.f, 0.55f}}, {65.f, {0.80f, 0.f, 0.55f}}, {100.f, {0.45f, 0.3f, 0.70f}}, {135.f, {0.05f, 1.f, 0.90f}},
|
||||
{170.f, {0.f, 0.4f, 1.f}}, {200.f, {0.10f, 0.f, 1.f}}, {240.f, {0.35f, 0.f, 1.f}}, {280.f, {0.50f, 0.f, 0.95f}},
|
||||
{320.f, {0.65f, 0.f, 0.80f}}, {341.f, {0.80f, 0.f, 0.60f}}, {367.f, {0.80f, 0.f, 0.55f}}};
|
||||
auto const day{std::clamp(static_cast<float>(Yearday), 1.f, 366.f)};
|
||||
for (std::size_t i = 1; i < std::size(year); ++i)
|
||||
if (day <= year[i].day)
|
||||
{
|
||||
auto const t{(day - year[i - 1].day) / (year[i].day - year[i - 1].day)};
|
||||
return glm::mix(year[i - 1].value, year[i].value, t * t * (3.f - 2.f * t));
|
||||
}
|
||||
return year[std::size(year) - 1].value;
|
||||
}
|
||||
|
||||
GLuint make_texture(GLenum const Format, int const Width, int const Height, GLenum const Filter)
|
||||
{
|
||||
GLuint texture{0};
|
||||
@@ -112,37 +73,28 @@ bool opengl46_grass::init()
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_grass::release(chunk &Chunk)
|
||||
void opengl46_grass::gpu_release(std::uint64_t const Id)
|
||||
{
|
||||
for (auto *texture : {&Chunk.heights, &Chunk.density, &Chunk.holes})
|
||||
auto const lookup{m_textures.find(Id)};
|
||||
if (lookup == m_textures.end())
|
||||
return;
|
||||
auto &chunk{lookup->second};
|
||||
for (auto *texture : {&chunk.heights, &chunk.density, &chunk.holes})
|
||||
if (*texture != 0)
|
||||
{
|
||||
::glDeleteTextures(1, texture);
|
||||
*texture = 0;
|
||||
}
|
||||
m_textures.erase(lookup);
|
||||
}
|
||||
|
||||
void opengl46_grass::clear()
|
||||
void opengl46_grass::gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source)
|
||||
{
|
||||
for (auto &entry : m_chunks)
|
||||
release(entry.second);
|
||||
m_chunks.clear();
|
||||
}
|
||||
|
||||
void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
|
||||
{
|
||||
release(Chunk);
|
||||
Chunk.version = Source.version;
|
||||
Chunk.corner = Source.corner;
|
||||
Chunk.size = Source.size;
|
||||
Chunk.cells = Source.cells;
|
||||
Chunk.samples = Source.samples;
|
||||
Chunk.kinds = Source.kinds;
|
||||
|
||||
auto &chunk{m_textures[Id]};
|
||||
auto const side{Source.cells + 1};
|
||||
Chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
|
||||
chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTextureSubImage2D(Chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
|
||||
::glTextureSubImage2D(chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
|
||||
|
||||
// the densities of the kinds side by side, as the channels of a texel
|
||||
auto const samples{Source.samples + 1};
|
||||
@@ -154,186 +106,57 @@ void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
for (std::size_t i = 0; i < plane; ++i)
|
||||
texels[i * 4 + kind] = Source.density[kind * plane + i];
|
||||
Chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
|
||||
::glTextureSubImage2D(Chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
|
||||
::glTextureSubImage2D(chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
|
||||
std::uint8_t const none{0};
|
||||
auto const holeside{(Source.holes > 0 && Source.hole_mask != nullptr) ? Source.holes + 1 : 1};
|
||||
Chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
|
||||
chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTextureSubImage2D(Chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
|
||||
::glTextureSubImage2D(chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
|
||||
// the ground and the densest place of every kind of each tile, to skip the empty tiles and to draw as many blades as needed
|
||||
auto const spacing{static_cast<double>(Source.size) / Source.cells};
|
||||
auto const step{static_cast<double>(Source.size) / Source.samples};
|
||||
auto const tilesize{static_cast<double>(Source.size) / tiles};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto &part{Chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const range = [](double const From, double const To, double const Step, int const Last) {
|
||||
return std::make_pair(std::clamp(static_cast<int>(std::floor(From / Step)), 0, Last), std::clamp(static_cast<int>(std::ceil(To / Step)), 0, Last));
|
||||
};
|
||||
auto const [gx0, gx1]{range(tx * tilesize, (tx + 1) * tilesize, spacing, Source.cells)};
|
||||
auto const [gz0, gz1]{range(tz * tilesize, (tz + 1) * tilesize, spacing, Source.cells)};
|
||||
part.low = std::numeric_limits<float>::max();
|
||||
part.high = -std::numeric_limits<float>::max();
|
||||
for (int z = gz0; z <= gz1; ++z)
|
||||
for (int x = gx0; x <= gx1; ++x)
|
||||
{
|
||||
auto const height{Source.heights[static_cast<std::size_t>(z) * side + x]};
|
||||
part.low = std::min(part.low, height);
|
||||
part.high = std::max(part.high, height);
|
||||
}
|
||||
part.most.fill(0.f);
|
||||
auto const [sx0, sx1]{range(tx * tilesize, (tx + 1) * tilesize, step, Source.samples)};
|
||||
auto const [sz0, sz1]{range(tz * tilesize, (tz + 1) * tilesize, step, Source.samples)};
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
{
|
||||
std::uint8_t most{0};
|
||||
for (int z = sz0; z <= sz1; ++z)
|
||||
for (int x = sx0; x <= sx1; ++x)
|
||||
most = std::max(most, Source.density[kind * plane + static_cast<std::size_t>(z) * samples + x]);
|
||||
part.most[kind] = most / 255.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_grass::update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks)
|
||||
{
|
||||
m_kinds = Kinds;
|
||||
for (auto &entry : m_chunks)
|
||||
entry.second.listed = false;
|
||||
if (false == Global.gfx_grass)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
for (auto const &source : Chunks)
|
||||
{
|
||||
if (source.heights == nullptr || source.density == nullptr || source.cells <= 0 || source.samples <= 0)
|
||||
continue;
|
||||
auto &entry{m_chunks[source.id]};
|
||||
entry.listed = true;
|
||||
if (entry.heights == 0 || entry.version != source.version)
|
||||
upload(entry, source);
|
||||
}
|
||||
// what's no longer near goes
|
||||
for (auto it = m_chunks.begin(); it != m_chunks.end();)
|
||||
{
|
||||
if (it->second.listed)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
release(it->second);
|
||||
it = m_chunks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t opengl46_grass::render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview)
|
||||
{
|
||||
if (false == Global.gfx_grass || m_chunks.empty() || m_kinds.empty() || false == init())
|
||||
if (false == prepare(Camera.position(), [&](scene::bounding_area const &Area) { return Camera.visible(Area); }) || false == init())
|
||||
return 0;
|
||||
|
||||
// whole up to the distance, thinning out to the fading distance
|
||||
auto const fade{std::max(5.f, Global.gfx_grass_distance)};
|
||||
auto const reach{std::max(fade + 1.f, Global.gfx_grass_fadedistance)};
|
||||
auto const thinning{std::max(3.f, fade * 0.2f)};
|
||||
auto const multiplier{std::max(0.f, Global.gfx_grass_density)};
|
||||
auto const camera{Camera.position()};
|
||||
|
||||
m_program->bind();
|
||||
m_vao->bind();
|
||||
::glDisable(GL_CULL_FACE);
|
||||
::glUniform4f(m_uniforms.lod, thinning, fade, reach, 0.f);
|
||||
auto const season{grass_season(simulation::Time.year_day())};
|
||||
::glUniform4f(m_uniforms.season, season.x, season.y, season.z, 0.f);
|
||||
// the wind sways the blades along it, harder in the gusts
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto const windspeed{glm::length(glm::vec2{wind.x, wind.z})};
|
||||
auto const winddirection{windspeed > 0.01f ? glm::vec2{wind.x, wind.z} / windspeed : glm::vec2{1.f, 0.f}};
|
||||
::glUniform4f(m_uniforms.wind, winddirection.x, winddirection.y, std::clamp(windspeed / 15.f, 0.f, 1.f), static_cast<float>(std::fmod(Timer::GetTime(), 3600.0)));
|
||||
auto const &frame{this->frame()};
|
||||
::glUniform4fv(m_uniforms.lod, 1, glm::value_ptr(frame.lod));
|
||||
::glUniform4fv(m_uniforms.season, 1, glm::value_ptr(frame.season));
|
||||
::glUniform4fv(m_uniforms.wind, 1, glm::value_ptr(frame.wind));
|
||||
|
||||
std::size_t triangles{0};
|
||||
for (auto &entry : m_chunks)
|
||||
auto const &tiles{tile_draws()};
|
||||
for (auto const &chunk : chunk_draws())
|
||||
{
|
||||
auto &chunk{entry.second};
|
||||
// the kinds of the chunk, and the share of the blades of each against its densest
|
||||
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
|
||||
glm::vec4 share{0.f};
|
||||
float densest{0.f};
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
if (chunk.kinds[i] >= 0 && chunk.kinds[i] < static_cast<int>(m_kinds.size()))
|
||||
densest = std::max(densest, m_kinds[chunk.kinds[i]].density);
|
||||
if (densest <= 0.f)
|
||||
continue;
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
// the chunk is drawn relative to its corner, the heights as they are
|
||||
Modelview(glm::translate(View, glm::vec3{chunk.offset}));
|
||||
::glUniform4fv(m_uniforms.chunk, 1, glm::value_ptr(chunk.chunk));
|
||||
::glUniform4fv(m_uniforms.camera, 1, glm::value_ptr(chunk.camera));
|
||||
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(chunk.base[0]));
|
||||
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(chunk.tip[0]));
|
||||
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(chunk.shape[0]));
|
||||
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(chunk.share));
|
||||
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(chunk.dry[0]));
|
||||
auto const &textures{m_textures[chunk.id]};
|
||||
GLuint const units[]{textures.heights, textures.density, textures.holes};
|
||||
::glBindTextures(0, 3, units);
|
||||
for (auto i = chunk.first; i < chunk.first + chunk.count; ++i)
|
||||
{
|
||||
if (chunk.kinds[i] < 0 || chunk.kinds[i] >= static_cast<int>(m_kinds.size()))
|
||||
continue;
|
||||
auto const &kind{m_kinds[chunk.kinds[i]]};
|
||||
base[i] = glm::vec4{kind.base, 0.f};
|
||||
tip[i] = glm::vec4{kind.tip, 0.f};
|
||||
shape[i] = glm::vec4{kind.height, kind.variation, kind.width, kind.bend};
|
||||
dry[i] = glm::vec4{kind.dry, kind.seasonal};
|
||||
share[i] = kind.density / densest;
|
||||
auto const &tile{tiles[i]};
|
||||
::glUniform4fv(m_uniforms.tile, 1, glm::value_ptr(tile.tile));
|
||||
::glUniform4fv(m_uniforms.part, 1, glm::value_ptr(tile.part));
|
||||
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, tile.segments * 2 + 1, tile.count);
|
||||
}
|
||||
auto const tallest{std::max({shape[0].x, shape[1].x, shape[2].x, shape[3].x}) * 1.5f + blade_reach};
|
||||
|
||||
auto const tilesize{chunk.size / tiles};
|
||||
bool set{false};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto const &part{chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const fill{std::min(1.f, glm::dot(glm::vec4{part.most[0], part.most[1], part.most[2], part.most[3]}, share))};
|
||||
if (fill <= 0.f)
|
||||
continue;
|
||||
// distance to the box of the tile
|
||||
glm::dvec3 const low{chunk.corner.x + tx * tilesize, part.low, chunk.corner.y + tz * tilesize};
|
||||
glm::dvec3 const high{low.x + tilesize, part.high + tallest, low.z + tilesize};
|
||||
auto const nearest{glm::clamp(camera, low, high)};
|
||||
auto const distance{static_cast<float>(glm::length(nearest - camera))};
|
||||
if (distance > reach)
|
||||
continue;
|
||||
if (false == Camera.visible(scene::bounding_area{(low + high) * 0.5, static_cast<float>(glm::length(high - low) * 0.5)}))
|
||||
continue;
|
||||
auto const full{tilesize * tilesize * densest * multiplier * fill};
|
||||
auto const count{std::min(max_blades, static_cast<int>(std::ceil(full * lod_share(distance, thinning, fade, reach))))};
|
||||
if (count <= 0)
|
||||
continue;
|
||||
if (false == set)
|
||||
{
|
||||
// the chunk is drawn relative to its corner, the heights as they are
|
||||
set = true;
|
||||
Modelview(glm::translate(View, glm::vec3{glm::dvec3{chunk.corner.x - camera.x, -camera.y, chunk.corner.y - camera.z}}));
|
||||
::glUniform4f(m_uniforms.chunk, chunk.size, static_cast<float>(chunk.cells), static_cast<float>(chunk.samples), 0.f);
|
||||
::glUniform4f(m_uniforms.camera, static_cast<float>(camera.x - chunk.corner.x), static_cast<float>(camera.y), static_cast<float>(camera.z - chunk.corner.y), 0.f);
|
||||
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(base[0]));
|
||||
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(tip[0]));
|
||||
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(shape[0]));
|
||||
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(share));
|
||||
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(dry[0]));
|
||||
GLuint const textures[]{chunk.heights, chunk.density, chunk.holes};
|
||||
::glBindTextures(0, 3, textures);
|
||||
}
|
||||
// fewer segments further away
|
||||
auto const segments{distance < thinning * 1.5f ? 3 : distance < fade * 0.5f ? 2 : 1};
|
||||
// the number of the tile in the world, the seed of its blades
|
||||
auto const seedx{static_cast<float>(std::floor(chunk.corner.x / tilesize + 0.5) + tx)};
|
||||
auto const seedz{static_cast<float>(std::floor(chunk.corner.y / tilesize + 0.5) + tz)};
|
||||
::glUniform4f(m_uniforms.tile, tx * tilesize, tz * tilesize, tilesize, std::max(1.f, full));
|
||||
::glUniform4f(m_uniforms.part, fill, static_cast<float>(segments), seedx, seedz);
|
||||
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, segments * 2 + 1, count);
|
||||
triangles += static_cast<std::size_t>(count) * (segments * 2 - 1);
|
||||
}
|
||||
}
|
||||
|
||||
::glEnable(GL_CULL_FACE);
|
||||
gl::vao::unbind();
|
||||
// the units of the textures were taken behind the back of the texture cache
|
||||
opengl_texture::reset_unit_cache();
|
||||
return triangles;
|
||||
return triangles();
|
||||
}
|
||||
|
||||
@@ -13,69 +13,44 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/common/grassfield.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/vao.h"
|
||||
|
||||
class opengl_camera;
|
||||
|
||||
// grass of the heightmap terrain for the opengl 4.6 renderer: blades made up by the vertex shader (grass.vert) where the
|
||||
// grass is painted, in the chunks near the camera.
|
||||
//
|
||||
// a chunk is cut into tiles; each tile visible within the reach of the grass is drawn as instances of a blade, as many as
|
||||
// its densest kind and its distance ask for. a blade finds its place, its kind and its shape from its number, so the same
|
||||
// blades stand at the same places in every frame. further away the blades thin out and grow wider, and fade out at the end
|
||||
// of the reach. the heights, the densities and the holes of a chunk are kept in textures, made when the chunk comes near
|
||||
class opengl46_grass
|
||||
// grass of the heightmap terrain for the opengl 4.6 renderer (gfx::grass_field): the textures of the chunks, and the draws of
|
||||
// the tiles seen, instances of a blade made up by grass.vert
|
||||
class opengl46_grass : public gfx::grass_field
|
||||
{
|
||||
public:
|
||||
opengl46_grass() = default;
|
||||
~opengl46_grass();
|
||||
~opengl46_grass() override;
|
||||
|
||||
// see gfx_renderer::Terrain_Grass()
|
||||
void update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks);
|
||||
// draws the grass seen by the camera. View: the modelview of the pass with the camera at its origin; Modelview: hands a
|
||||
// modelview to the model block of the draws which follow. returns: triangles drawn
|
||||
std::size_t render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview);
|
||||
// drops everything made on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
bool empty() const { return m_chunks.empty(); }
|
||||
|
||||
static int constexpr tiles{16}; // along a side of a chunk
|
||||
protected:
|
||||
void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) override;
|
||||
void gpu_release(std::uint64_t const Id) override;
|
||||
|
||||
private:
|
||||
struct tile
|
||||
struct chunk_textures
|
||||
{
|
||||
float low{0.f}, high{0.f}; // of the ground
|
||||
std::array<float, 4> most{}; // the highest density of each kind of the chunk within the tile, 0..1
|
||||
};
|
||||
struct chunk
|
||||
{
|
||||
std::uint64_t version{0};
|
||||
glm::dvec2 corner{0.0};
|
||||
float size{0.f};
|
||||
int cells{0};
|
||||
int samples{0};
|
||||
std::array<int, 4> kinds{-1, -1, -1, -1};
|
||||
GLuint heights{0}; // R32F, the grid points
|
||||
GLuint density{0}; // RGBA8, the densities of the kinds
|
||||
GLuint holes{0}; // R8, a single texel of 0 without holes
|
||||
std::array<tile, tiles * tiles> parts;
|
||||
bool listed{false}; // in the last list given
|
||||
};
|
||||
|
||||
bool init();
|
||||
void upload(chunk &Chunk, gfx::grass_chunk const &Source);
|
||||
void release(chunk &Chunk);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
std::unique_ptr<gl::vao> m_vao; // without attributes, the blades are made of the numbers of their vertices
|
||||
std::vector<gfx::grass_kind> m_kinds;
|
||||
std::unordered_map<std::uint64_t, chunk> m_chunks;
|
||||
std::unordered_map<std::uint64_t, chunk_textures> m_textures;
|
||||
// locations of the uniforms of the program
|
||||
struct locations
|
||||
{
|
||||
|
||||
@@ -10,10 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46instancecull.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "model/Texture.h"
|
||||
#include "rendering/frustum.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
@@ -36,6 +33,7 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
|
||||
if (Shared != nullptr)
|
||||
{
|
||||
m_shared = Shared->m_shared;
|
||||
share_records(*Shared);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -59,203 +57,11 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
|
||||
m_output = std::make_unique<gl::ssbo>(65536 * sizeof(glm::mat4), gl::INSTANCE_OUTPUT_SSBO_BINDING);
|
||||
m_commands = std::make_unique<gl::ssbo>(4096 * sizeof(gl::instance_draw_command), gl::INSTANCE_COMMAND_SSBO_BINDING);
|
||||
m_readback = std::make_unique<gl::readback>(readbacksize);
|
||||
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
|
||||
{
|
||||
collect();
|
||||
|
||||
m_inpass = true;
|
||||
m_ran = true;
|
||||
m_dispatched = false;
|
||||
m_setup = Setup;
|
||||
m_buckets.clear();
|
||||
m_joblist.clear();
|
||||
m_grouplist.clear();
|
||||
m_drawgroups.clear();
|
||||
m_lodlist.clear();
|
||||
m_groupmap.clear();
|
||||
m_sleepergroupmap.clear();
|
||||
m_cpucounts.clear();
|
||||
m_candidates = 0;
|
||||
m_entries = 0;
|
||||
m_sleepertracks = 0;
|
||||
m_sleepers = 0;
|
||||
m_models = 0;
|
||||
m_counts = {};
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, false);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
if (!m_inpass)
|
||||
return;
|
||||
++m_counts.skippedbuckets;
|
||||
m_counts.skippedinstances += Bucket.records.size();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, true);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
|
||||
{
|
||||
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
|
||||
return;
|
||||
|
||||
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
|
||||
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
|
||||
// camera of the pass
|
||||
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
|
||||
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
|
||||
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
|
||||
auto const centerdistance{glm::length(Area.center - distancecamera)};
|
||||
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
|
||||
auto const farthest{centerdistance + Area.radius};
|
||||
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
|
||||
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
|
||||
|
||||
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
{
|
||||
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
|
||||
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
|
||||
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
|
||||
|
||||
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
||||
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
||||
m_entries += group.entrycount;
|
||||
lookup = groupmap.emplace(Key, group).first;
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
// any instance of the group serves; it's only prepared and asked for its material
|
||||
TAnimModel *instance{nullptr};
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lookup->second.distancemin = std::min(lookup->second.distancemin, distancemin);
|
||||
lookup->second.distancemax = std::max(lookup->second.distancemax, distancemax);
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
auto &drawgroup{m_drawgroups[lookup->second.index]};
|
||||
drawgroup.distancemin = lookup->second.distancemin;
|
||||
drawgroup.distancemax = lookup->second.distancemax;
|
||||
if (drawgroup.instance == nullptr)
|
||||
{
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
drawgroup.instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
place(Bucket);
|
||||
|
||||
gl::instance_cull_job job{};
|
||||
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
|
||||
job.first = Bucket.gpufirst;
|
||||
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
|
||||
job.count = static_cast<std::uint32_t>(Bucket.records.size());
|
||||
job.group = lookup->second.index;
|
||||
job.candidatestart = m_candidates;
|
||||
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
|
||||
m_joblist.push_back(job);
|
||||
m_buckets.push_back(&Bucket);
|
||||
m_candidates += job.count;
|
||||
if (Sleepers)
|
||||
{
|
||||
++m_sleepertracks;
|
||||
m_sleepers += job.count;
|
||||
}
|
||||
else if (Key.instance != nullptr)
|
||||
{
|
||||
++m_models;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
auto &shared{*m_shared};
|
||||
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
|
||||
auto const current{Bucket.gpugeneration == shared.recordgeneration};
|
||||
if (current && !Bucket.gpustale)
|
||||
return;
|
||||
if (current && count <= Bucket.gpucapacity)
|
||||
{
|
||||
shared.records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
|
||||
m_counts.sentrecords += count;
|
||||
Bucket.gpustale = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// a new place, with some room for instances the editor adds
|
||||
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
|
||||
auto const records{static_cast<std::uint32_t>(Target.records.size())};
|
||||
Target.gpufirst = shared.recordfill;
|
||||
Target.gpucapacity = records + records / 4;
|
||||
Target.gpugeneration = shared.recordgeneration;
|
||||
Target.gpustale = false;
|
||||
shared.recordfill += Target.gpucapacity;
|
||||
shared.records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
|
||||
m_counts.sentrecords += records;
|
||||
};
|
||||
|
||||
auto const capacity{static_cast<std::uint32_t>(shared.records->size() / RECORD_SIZE)};
|
||||
if (shared.recordfill + count + count / 4 > capacity)
|
||||
{
|
||||
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
|
||||
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
|
||||
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
|
||||
// so a buffer started over again in the same or the next frame grows
|
||||
GLsizeiptr live{count + count / 4};
|
||||
for (auto const *bucket : m_buckets)
|
||||
live += bucket->records.size() + bucket->records.size() / 4;
|
||||
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
|
||||
if ((2 * live * RECORD_SIZE > shared.records->size()) || again)
|
||||
shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size()));
|
||||
shared.restarted = true;
|
||||
shared.restartframe = m_setup.frame;
|
||||
m_counts.restarted = true;
|
||||
++shared.recordgeneration;
|
||||
shared.recordfill = 0;
|
||||
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||
{
|
||||
allocate(*m_buckets[job]);
|
||||
m_joblist[job].first = m_buckets[job]->gpufirst;
|
||||
}
|
||||
}
|
||||
allocate(Bucket);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion, bool const Twophase)
|
||||
{
|
||||
m_occlusion = false;
|
||||
m_twophase = false;
|
||||
if (!m_inpass || m_joblist.empty())
|
||||
if (!dispatch_begin())
|
||||
return;
|
||||
|
||||
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t))};
|
||||
@@ -277,14 +83,8 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
bind_buffers();
|
||||
|
||||
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
||||
auto const &frustumplanes{m_setup.frustum->planes()};
|
||||
glm::vec4 planes[6];
|
||||
for (int plane = 0; plane < 6; ++plane)
|
||||
{
|
||||
auto const *source{frustumplanes[plane]};
|
||||
planes[plane] = glm::vec4{source[0], source[1], source[2],
|
||||
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
|
||||
}
|
||||
frustum_planes(planes);
|
||||
glProgramUniformMatrix4fv(*m_shared->cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
|
||||
glProgramUniform4fv(*m_shared->cull_program, 1, 6, glm::value_ptr(planes[0]));
|
||||
glProgramUniform4f(*m_shared->cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
|
||||
@@ -328,8 +128,7 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
m_cpucounts.assign(m_entries, 0);
|
||||
m_dispatched = true;
|
||||
dispatch_end();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::bind_buffers()
|
||||
@@ -401,144 +200,29 @@ void opengl46_instance_cull::bind_draw()
|
||||
glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &output);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
|
||||
std::int64_t opengl46_instance_cull::record_buffer_size() const
|
||||
{
|
||||
m_draws = Draws;
|
||||
m_prepassdraws = Prepassdraws;
|
||||
m_calls = Calls;
|
||||
m_prepasscalls = Prepasscalls;
|
||||
m_cputime = Cputime;
|
||||
return m_shared->records->size();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
|
||||
void opengl46_instance_cull::resize_record_buffer(std::int64_t const Size)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto const lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
|
||||
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
|
||||
m_shared->records->resize(static_cast<GLsizeiptr>(Size));
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
|
||||
void opengl46_instance_cull::send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const lookup{m_groupmap.find(Key)};
|
||||
if (lookup == m_groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
auto const &lods{m_grouplist[group.index]};
|
||||
// the band the culling gives the distance: the number of bounds it reached
|
||||
auto const first{m_lodlist.begin() + lods.lodfirst};
|
||||
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
|
||||
if (band < group.entrycount)
|
||||
++m_cpucounts[group.entryfirst + band];
|
||||
m_shared->records->update(Records, static_cast<GLintptr>(First) * RECORD_SIZE, static_cast<GLsizeiptr>(Count) * RECORD_SIZE);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::end_pass()
|
||||
void opengl46_instance_cull::push_counts()
|
||||
{
|
||||
// the counts of both phases, for the panel and the comparison
|
||||
if (m_dispatched)
|
||||
m_readback->push(*m_counters);
|
||||
// the cpu counts are there to compare with only when the cpu drew
|
||||
if (m_dispatched && !m_setup.drawing)
|
||||
{
|
||||
std::uint64_t total{0};
|
||||
for (auto const count : m_cpucounts)
|
||||
total += count;
|
||||
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
|
||||
// the gpu is never this far behind
|
||||
while (m_cpupasses.size() > 8)
|
||||
m_cpupasses.pop_front();
|
||||
}
|
||||
m_lastsleepertracks = m_sleepertracks;
|
||||
m_lastsleepers = m_sleepers;
|
||||
m_lastmodels = m_models;
|
||||
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
|
||||
m_counts.candidates = m_candidates;
|
||||
m_lastcounts = m_counts;
|
||||
m_inpass = false;
|
||||
m_dispatched = false;
|
||||
m_readback->push(*m_counters);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::collect()
|
||||
bool opengl46_instance_cull::pop_counts(std::uint32_t *Counts)
|
||||
{
|
||||
if (!m_readback || !m_readback->pop(m_readbackdata.data()))
|
||||
return;
|
||||
|
||||
auto const frame{m_readbackdata[0]};
|
||||
comparison result;
|
||||
result.frame = frame;
|
||||
result.occluded = m_readbackdata[3];
|
||||
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
|
||||
result.entries = m_readbackdata[2];
|
||||
result.first = m_readbackdata[4];
|
||||
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||
m_cpupasses.pop_front();
|
||||
if (m_cpupasses.empty())
|
||||
{
|
||||
// drawn from the gpu: nothing to compare with
|
||||
m_last = result;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const &cpu{m_cpupasses.front()};
|
||||
result.cputotal = cpu.total;
|
||||
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
|
||||
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
|
||||
{
|
||||
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
|
||||
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
|
||||
if (difference > 0)
|
||||
{
|
||||
++result.mismatched;
|
||||
result.largestdifference = std::max(result.largestdifference, difference);
|
||||
}
|
||||
}
|
||||
m_cpupasses.pop_front();
|
||||
|
||||
m_last = result;
|
||||
++m_comparedframes;
|
||||
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
|
||||
++m_mismatchedframes;
|
||||
}
|
||||
|
||||
std::string opengl46_instance_cull::info(std::string const &Label, bool const Occlusion)
|
||||
{
|
||||
if (Global.gfx_gpucull == 0)
|
||||
return {};
|
||||
// the sleepers are among the instances counted
|
||||
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
|
||||
// and so are the models drawn on their own
|
||||
if (m_lastmodels > 0)
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
|
||||
// what the pass tested and sent to the gpu
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
|
||||
if (Global.gfx_gpucull_bucketrange)
|
||||
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling";
|
||||
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
if (m_last.frame == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
if (false == Occlusion)
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
|
||||
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
|
||||
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
|
||||
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
|
||||
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
|
||||
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
}
|
||||
if (m_comparedframes == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
|
||||
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
|
||||
std::to_string(m_comparedframes) + sleepers;
|
||||
return (m_readback && m_readback->pop(Counts));
|
||||
}
|
||||
|
||||
std::string opengl46_instance_cull::info_times(std::string const &Label)
|
||||
|
||||
@@ -21,53 +21,15 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/query.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "scene/scene.h"
|
||||
#include "rendering/common/instancecull.h"
|
||||
|
||||
class cFrustum;
|
||||
class TAnimModel;
|
||||
|
||||
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp),
|
||||
// the gpu side of opengl46_renderer::Queue_Instances() and Render_Instanced():
|
||||
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
|
||||
// in its own place, and sent again only when the bucket made them anew
|
||||
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
|
||||
// lod bands are its entries
|
||||
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
|
||||
// modelview matrices of the instances there
|
||||
// with Global.gfx_gpucull 1 the instances are drawn from the result: the renderer turns each lod band of a group into a
|
||||
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
|
||||
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer
|
||||
// (opengl46_renderer::Render_Instances_Indirect()). an occlusion test against a depth pyramid of what the pass drew before
|
||||
// (opengl46_hiz) can leave out more instances. in two phases the instances seen in the previous frame are culled and drawn
|
||||
// first, then the pyramid is made again with them in it (dispatch_second()) and the others are tested against it, so the
|
||||
// instances hide one another too. the command buffer holds the commands three times over: the instances drawn first, all
|
||||
// of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
|
||||
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
|
||||
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
|
||||
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map, the
|
||||
// shadow maps of headlights and the scenery parts of those of lamps) has a culling of its own, with its own buffers, while the
|
||||
// records and the programs are shared by all of them: the records of a bucket go to the gpu once and serve every pass which
|
||||
// queues the bucket
|
||||
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
|
||||
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
|
||||
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
|
||||
// its key), whose submodel tree the renderer animates and walks for it alone
|
||||
class opengl46_instance_cull
|
||||
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp,
|
||||
// instance_cull_commands.comp): the buffers and the passes of gfx::instance_cull, which tells what they do. with
|
||||
// Global.gfx_gpucull 1 the renderer draws the result with opengl46_renderer::Render_Instances_Indirect(); the occlusion test
|
||||
// takes the depth pyramid of opengl46_hiz
|
||||
class opengl46_instance_cull : public gfx::instance_cull
|
||||
{
|
||||
public:
|
||||
// what a pass needs for the tests, the way Render_Instanced() takes it
|
||||
struct pass_setup
|
||||
{
|
||||
unsigned int frame{0};
|
||||
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
|
||||
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
|
||||
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
|
||||
float distancescale{1.f}; // of the squared distance
|
||||
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
|
||||
float distancerange{0.f}; // squared range of the scaled distance before the offset
|
||||
float drawrange{0.f}; // squared draw range
|
||||
float zoom{1.f};
|
||||
bool drawing{false}; // the instances are drawn from the result (Global.gfx_gpucull 1), not compared
|
||||
};
|
||||
// occlusion test of a pass
|
||||
struct occlusion_setup
|
||||
{
|
||||
@@ -77,47 +39,11 @@ class opengl46_instance_cull
|
||||
glm::vec2 screen{0.f}; // size of the depth it was made from, pixels
|
||||
glm::mat4 projection{1.f}; // of the pass
|
||||
};
|
||||
// a group of instances (model and skins) of the pass, for the renderer to make the draw commands of
|
||||
struct draw_group
|
||||
{
|
||||
scene::basic_cell::instance_bucket_key const *key;
|
||||
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
|
||||
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
|
||||
std::uint32_t lodfirst; // in lod_bounds()
|
||||
std::uint32_t lodcount;
|
||||
std::uint32_t entryfirst;
|
||||
float distancemin; // squared distances (scaled as the culling does) the instances of the group can be at
|
||||
float distancemax;
|
||||
};
|
||||
|
||||
opengl46_instance_cull() = default;
|
||||
opengl46_instance_cull(const opengl46_instance_cull &) = delete;
|
||||
opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete;
|
||||
|
||||
// creates the buffers, and the programs and the record buffer, or takes the ones of Shared. throws gl::shader_exception
|
||||
void init(opengl46_instance_cull const *Shared = nullptr);
|
||||
// starts collecting the buckets of a pass
|
||||
void begin_pass(pass_setup const &Setup);
|
||||
bool in_pass() const
|
||||
{
|
||||
return m_inpass;
|
||||
}
|
||||
bool drawing() const
|
||||
{
|
||||
return m_inpass && m_setup.drawing;
|
||||
}
|
||||
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed.
|
||||
// Area: bounds which hold the locations of all its visible instances: their own (opengl46_renderer::Bucket_Area()) or of the cell
|
||||
// holding the bucket
|
||||
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place
|
||||
// in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by
|
||||
// the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the
|
||||
// camera of the pass (Render_Sleepers())
|
||||
void add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted,
|
||||
// for the panel
|
||||
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid
|
||||
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
|
||||
// frame pass for now, dispatch_second() takes the others
|
||||
@@ -126,108 +52,22 @@ class opengl46_instance_cull
|
||||
// the others are tested against it, then the commands of all instances and of the ones of this phase are filled in.
|
||||
// needs fill_commands() before it
|
||||
void dispatch_second(occlusion_setup const &Occlusion);
|
||||
// the last dispatch() works in two phases
|
||||
bool two_phase() const
|
||||
{
|
||||
return m_twophase;
|
||||
}
|
||||
// groups of the pass and the lod bounds they refer to, valid from dispatch() to end_pass()
|
||||
std::vector<draw_group> const &draw_groups() const
|
||||
{
|
||||
return m_drawgroups;
|
||||
}
|
||||
std::vector<float> const &lod_bounds() const
|
||||
{
|
||||
return m_lodlist;
|
||||
}
|
||||
// sends the indirect draw commands made for the entries of the pass and fills in their instances (of the instances drawn
|
||||
// first, in two phases). after this the commands are in the buffer bound by bind_draw(), the modelview matrices of the
|
||||
// instances in the output
|
||||
void fill_commands(std::vector<gl::instance_draw_command> const &Commands);
|
||||
// copies of the commands in the command buffer
|
||||
enum class command_set : std::uint32_t
|
||||
{
|
||||
first = 0, // instances which passed dispatch(): in one phase all of them
|
||||
all = 1, // after dispatch_second()
|
||||
second = 2 // the ones which passed dispatch_second()
|
||||
};
|
||||
// offset of command Index of a copy in the command buffer
|
||||
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
|
||||
{
|
||||
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
|
||||
}
|
||||
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
|
||||
void bind_draw();
|
||||
// counts of the last result which came back from the gpu, with the draws of the pass and the gl calls which made them (fewer
|
||||
// with multi draws), for the debug panel
|
||||
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
|
||||
// what draw_result() was given last
|
||||
std::uint32_t prepass_draws() const
|
||||
{
|
||||
return m_prepassdraws;
|
||||
}
|
||||
std::uint32_t prepass_calls() const
|
||||
{
|
||||
return m_prepasscalls;
|
||||
}
|
||||
double cpu_time() const
|
||||
{
|
||||
return m_cputime;
|
||||
}
|
||||
// instances which passed in the last result which came back from the gpu, a few frames old
|
||||
std::uint32_t last_count() const
|
||||
{
|
||||
return static_cast<std::uint32_t>(m_last.gputotal);
|
||||
}
|
||||
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
|
||||
// Render_Sleepers() drew. needs dispatch() before it
|
||||
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
|
||||
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
|
||||
// scales it: counted in the lod band the distance falls in. needs dispatch() before it
|
||||
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
|
||||
// ends the pass: its cpu counts are kept for the comparison
|
||||
void end_pass();
|
||||
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
|
||||
// for the panel also by the culls of passes which aren't made every frame
|
||||
void collect();
|
||||
// a pass was culled since the start
|
||||
bool ran() const
|
||||
{
|
||||
return m_ran;
|
||||
}
|
||||
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
|
||||
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
|
||||
std::string info_times(std::string const &Label = "instance cull");
|
||||
|
||||
private:
|
||||
struct group_data
|
||||
{
|
||||
std::uint32_t index;
|
||||
std::uint32_t entryfirst;
|
||||
std::uint32_t entrycount;
|
||||
float distancemin;
|
||||
float distancemax;
|
||||
};
|
||||
// counts of the cpu of a pass, waiting for those of the gpu
|
||||
struct cpu_pass
|
||||
{
|
||||
unsigned int frame;
|
||||
std::vector<std::uint32_t> counts;
|
||||
std::uint64_t total;
|
||||
};
|
||||
struct comparison
|
||||
{
|
||||
unsigned int frame{0};
|
||||
std::uint64_t gputotal{0};
|
||||
std::uint64_t cputotal{0};
|
||||
std::uint32_t entries{0};
|
||||
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
|
||||
std::uint32_t mismatched{0};
|
||||
std::uint32_t largestdifference{0};
|
||||
std::uint32_t occluded{0}; // left out by the occlusion test
|
||||
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
|
||||
};
|
||||
protected:
|
||||
std::int64_t record_buffer_size() const override;
|
||||
void resize_record_buffer(std::int64_t const Size) override;
|
||||
void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) override;
|
||||
void push_counts() override;
|
||||
bool pop_counts(std::uint32_t *Counts) override;
|
||||
|
||||
private:
|
||||
// what the culls of all passes share
|
||||
struct shared_data
|
||||
{
|
||||
@@ -235,26 +75,11 @@ class opengl46_instance_cull
|
||||
std::unique_ptr<gl::program> offsets_program;
|
||||
std::unique_ptr<gl::program> commands_program;
|
||||
std::unique_ptr<gl::ssbo> records;
|
||||
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
|
||||
std::uint32_t recordfill{0};
|
||||
unsigned int recordgeneration{1};
|
||||
// frame of the last time the buffer was started over, and whether it was
|
||||
unsigned int restartframe{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
|
||||
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
|
||||
|
||||
// add_bucket() and add_sleepers()
|
||||
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
|
||||
// finds a place for the records of a bucket in the record buffer and sends them
|
||||
void place(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// binds the buffers of the culling passes
|
||||
void bind_buffers();
|
||||
|
||||
// entries read back for the comparison; with more of them only the totals and these are compared
|
||||
static constexpr std::uint32_t READBACK_ENTRIES{16384};
|
||||
|
||||
std::shared_ptr<shared_data> m_shared;
|
||||
std::unique_ptr<gl::ssbo> m_jobs;
|
||||
std::unique_ptr<gl::ssbo> m_groups;
|
||||
@@ -265,55 +90,4 @@ class opengl46_instance_cull
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
gl::gpu_timer m_timer;
|
||||
gl::gpu_timer m_secondtimer;
|
||||
|
||||
bool m_inpass{false};
|
||||
bool m_ran{false};
|
||||
pass_setup m_setup;
|
||||
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
|
||||
std::vector<gl::instance_cull_job> m_joblist;
|
||||
std::vector<gl::instance_cull_group> m_grouplist;
|
||||
std::vector<draw_group> m_drawgroups;
|
||||
std::vector<float> m_lodlist;
|
||||
group_map m_groupmap;
|
||||
group_map m_sleepergroupmap; // groups of the sleepers
|
||||
std::uint32_t m_candidates{0};
|
||||
std::uint32_t m_entries{0};
|
||||
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_sleepertracks{0};
|
||||
std::uint32_t m_sleepers{0};
|
||||
std::uint32_t m_lastsleepertracks{0};
|
||||
std::uint32_t m_lastsleepers{0};
|
||||
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_models{0};
|
||||
std::uint32_t m_lastmodels{0};
|
||||
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
|
||||
// their range and their instances, records sent to the gpu, the record buffer started over
|
||||
struct pass_counts
|
||||
{
|
||||
std::uint32_t jobs{0};
|
||||
std::uint32_t candidates{0};
|
||||
std::uint32_t skippedbuckets{0};
|
||||
std::uint64_t skippedinstances{0};
|
||||
std::uint64_t sentrecords{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
pass_counts m_counts;
|
||||
pass_counts m_lastcounts;
|
||||
bool m_dispatched{false};
|
||||
|
||||
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
|
||||
std::deque<cpu_pass> m_cpupasses;
|
||||
std::vector<std::uint32_t> m_readbackdata;
|
||||
comparison m_last;
|
||||
std::uint64_t m_comparedframes{0};
|
||||
std::uint64_t m_mismatchedframes{0};
|
||||
// draws of the last pass drawn from the gpu
|
||||
std::uint32_t m_draws{0};
|
||||
std::uint32_t m_prepassdraws{0};
|
||||
std::uint32_t m_calls{0};
|
||||
std::uint32_t m_prepasscalls{0};
|
||||
std::uint32_t m_commandcount{0};
|
||||
double m_cputime{0.0};
|
||||
bool m_occlusion{false}; // the last dispatch tested occlusion
|
||||
bool m_twophase{false}; // in two phases
|
||||
};
|
||||
|
||||
@@ -19,17 +19,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
|
||||
float const SHADOW_NEAR{0.15f};
|
||||
// near plane of the maps (gfx::local_shadows)
|
||||
float const SHADOW_NEAR{gfx::local_shadows::shadow_near};
|
||||
// uniform locations of shadowcube.geom
|
||||
GLint const NEAR_LOCATION{0};
|
||||
GLint const LAYER_LOCATION{1};
|
||||
// layer of the map of the cab lamp in the array
|
||||
int const CAB_SLOT{0};
|
||||
// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres
|
||||
double const LAMP_MOVED{0.01};
|
||||
// lamps with a shorter reach aren't worth a map, metres
|
||||
float const LAMP_RANGE_MIN{1.f};
|
||||
int const CAB_SLOT{gfx::local_shadows::cab_slot};
|
||||
|
||||
// cube map array of depth maps compared to the reference, one level, Cubes cubes of Size x Size faces
|
||||
GLuint make_cube_array(int const Size, int const Cubes)
|
||||
@@ -99,9 +95,9 @@ void setup_drawing(GLuint const Framebuffer, int const Size)
|
||||
|
||||
opengl46_local_shadows::~opengl46_local_shadows()
|
||||
{
|
||||
release();
|
||||
release_lamps();
|
||||
release_headlights();
|
||||
gpu_release();
|
||||
gpu_release_lamps();
|
||||
gpu_release_headlights();
|
||||
if (m_empty != 0)
|
||||
glDeleteTextures(1, &m_empty);
|
||||
if (m_emptyheadlights != 0)
|
||||
@@ -167,40 +163,14 @@ void opengl46_local_shadows::set_layer(int const Layer)
|
||||
glProgramUniform1i(*m_alpha_multidraw_program, LAYER_LOCATION, Layer);
|
||||
}
|
||||
|
||||
opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Level)
|
||||
bool opengl46_local_shadows::gpu_allocate(int const Size)
|
||||
{
|
||||
switch (Level)
|
||||
{
|
||||
case 1:
|
||||
return {512, 4, 0.f, 1, 1024, 2, 8};
|
||||
case 2:
|
||||
return {1024, 2, 1.f, 1, 1024, 1, 4};
|
||||
case 3:
|
||||
return {1024, 1, 2.f, 2, 2048, 1, 2};
|
||||
default:
|
||||
return {0, 0, 0.f, 0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate(int const Size)
|
||||
{
|
||||
release();
|
||||
if (Size <= 0)
|
||||
return;
|
||||
m_texture = make_cube_array(Size, CAB_SLOT + 1);
|
||||
m_framebuffer = make_framebuffer(m_texture);
|
||||
if (m_framebuffer == 0)
|
||||
{
|
||||
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
|
||||
release();
|
||||
Global.gfx_lights_shadows = 0;
|
||||
return;
|
||||
}
|
||||
m_size = Size;
|
||||
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
|
||||
return (m_framebuffer != 0);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release()
|
||||
void opengl46_local_shadows::gpu_release()
|
||||
{
|
||||
if (m_framebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_framebuffer);
|
||||
@@ -208,32 +178,17 @@ void opengl46_local_shadows::release()
|
||||
glDeleteTextures(1, &m_texture);
|
||||
m_framebuffer = 0;
|
||||
m_texture = 0;
|
||||
m_size = 0;
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate_lamps(int const Count, int const Size)
|
||||
bool opengl46_local_shadows::gpu_allocate_lamps(int const Count, int const Size)
|
||||
{
|
||||
release_lamps();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
// the scenery parts and the working copies
|
||||
m_lampstexture = make_cube_array(Size, 2 * Count);
|
||||
m_lampsframebuffer = make_framebuffer(m_lampstexture);
|
||||
if (m_lampsframebuffer == 0)
|
||||
{
|
||||
ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows");
|
||||
release_lamps();
|
||||
Global.gfx_lights_shadows_lamps = 0;
|
||||
return;
|
||||
}
|
||||
m_lampssize = Size;
|
||||
m_slots.assign(static_cast<std::size_t>(Count), lamp_slot{});
|
||||
WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
|
||||
std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
return (m_lampsframebuffer != 0);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release_lamps()
|
||||
void opengl46_local_shadows::gpu_release_lamps()
|
||||
{
|
||||
if (m_lampsframebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_lampsframebuffer);
|
||||
@@ -241,35 +196,19 @@ void opengl46_local_shadows::release_lamps()
|
||||
glDeleteTextures(1, &m_lampstexture);
|
||||
m_lampsframebuffer = 0;
|
||||
m_lampstexture = 0;
|
||||
m_lampssize = 0;
|
||||
m_slots.clear();
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate_headlights(int const Count, int const Size)
|
||||
bool opengl46_local_shadows::gpu_allocate_headlights(int const Count, int const Size)
|
||||
{
|
||||
release_headlights();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
m_headlightstexture = make_array(Size, Count);
|
||||
glCreateFramebuffers(1, &m_headlightsframebuffer);
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, 0);
|
||||
glNamedFramebufferDrawBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
glNamedFramebufferReadBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
if (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
|
||||
release_headlights();
|
||||
Global.gfx_lights_shadows_headlights = 0;
|
||||
return;
|
||||
}
|
||||
m_headlightssize = Size;
|
||||
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
|
||||
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
|
||||
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
return (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release_headlights()
|
||||
void opengl46_local_shadows::gpu_release_headlights()
|
||||
{
|
||||
if (m_headlightsframebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_headlightsframebuffer);
|
||||
@@ -277,9 +216,6 @@ void opengl46_local_shadows::release_headlights()
|
||||
glDeleteTextures(1, &m_headlightstexture);
|
||||
m_headlightsframebuffer = 0;
|
||||
m_headlightstexture = 0;
|
||||
m_headlightssize = 0;
|
||||
m_headlightslots.clear();
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::bind()
|
||||
@@ -289,42 +225,8 @@ void opengl46_local_shadows::bind()
|
||||
glBindTextureUnit(gl::HEADLIGHT_SHADOW_TEX, (m_headlightstexture != 0) ? m_headlightstexture : m_emptyheadlights);
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
|
||||
void opengl46_local_shadows::gpu_begin_cab()
|
||||
{
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
if (level != m_level)
|
||||
{
|
||||
m_level = level;
|
||||
auto const size{setup(level).size};
|
||||
if (size != m_size)
|
||||
allocate(size);
|
||||
m_valid = false;
|
||||
}
|
||||
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
|
||||
{
|
||||
// with the lamp off nothing reads the map; once it is on again the cab may have changed
|
||||
m_valid = false;
|
||||
return false;
|
||||
}
|
||||
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
// the moving parts of the cab are followed every few frames
|
||||
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_vehicle = Vehicle;
|
||||
m_cab = Cab;
|
||||
m_lamp = Lamp;
|
||||
m_drawnframe = Framestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin()
|
||||
{
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
m_timer.begin();
|
||||
set_layer(CAB_SLOT * 6);
|
||||
setup_drawing(m_framebuffer, m_size);
|
||||
@@ -332,308 +234,46 @@ void opengl46_local_shadows::begin()
|
||||
glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_cab()
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
gl::framebuffer::unbind();
|
||||
m_timer.end();
|
||||
m_valid = true;
|
||||
m_drawcalls = Drawcalls;
|
||||
++m_updatecount;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::weights() const
|
||||
void opengl46_local_shadows::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles)
|
||||
{
|
||||
// a face spans twice the distance from the lamp
|
||||
return glm::vec4{static_cast<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
m_frame = Framestamp;
|
||||
m_wanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0};
|
||||
auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_slots.size()) || (size != m_lampssize))
|
||||
{
|
||||
allocate_lamps(count, size);
|
||||
}
|
||||
if (m_slots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_sceneryleft = setup(level).sceneryperframe;
|
||||
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
|
||||
// the lights come the most important first: the first ones within reach get the slots
|
||||
std::vector<gfx::local_lights::light const *> picked;
|
||||
for (auto const &light : Lights)
|
||||
{
|
||||
if (picked.size() >= m_slots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
picked.emplace_back(&light);
|
||||
}
|
||||
// lamps which already have a slot keep it
|
||||
std::vector<gfx::local_lights::light const *> unplaced;
|
||||
for (auto const *light : picked)
|
||||
{
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
if (glm::length(slot.position - light->position) > LAMP_MOVED)
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of lamps no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = lamp_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.part = light->part;
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// the scene changed (events, the editor): the scenery parts are out of date
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
if (slot.scenery && (slot.version != Version))
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::reset_lamps()
|
||||
{
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
slot = lamp_slot{};
|
||||
}
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::scenery_wanted(std::size_t const Slot)
|
||||
{
|
||||
if (m_slots[Slot].scenery || (m_sceneryleft <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--m_sceneryleft;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_slots[Slot]};
|
||||
return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval));
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_slots[Slot]};
|
||||
auto const scenerycube{static_cast<int>(Slot)};
|
||||
auto const workingcube{static_cast<int>(m_slots.size() + Slot)};
|
||||
if (Vehicles)
|
||||
{
|
||||
// the vehicles go over the scenery
|
||||
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, workingcube * 6, m_lampssize, m_lampssize, 6);
|
||||
slot.vehicles = true;
|
||||
slot.vehiclesframe = Framestamp;
|
||||
++m_lampsvehicles;
|
||||
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Workingcube * 6, m_lampssize, m_lampssize, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
float const farthest{0.f};
|
||||
glClearTexSubImage(m_lampstexture, 0, 0, 0, scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
slot.scenery = true;
|
||||
slot.version = Version;
|
||||
slot.vehicles = false;
|
||||
++m_lampsscenery;
|
||||
glClearTexSubImage(m_lampstexture, 0, 0, 0, Scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
}
|
||||
set_layer((Vehicles ? workingcube : scenerycube) * 6);
|
||||
set_layer((Vehicles ? Workingcube : Scenerycube) * 6);
|
||||
setup_drawing(m_lampsframebuffer, m_lampssize);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end_lamp(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_lamp()
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
gl::framebuffer::unbind();
|
||||
set_layer(CAB_SLOT * 6);
|
||||
m_lampsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::lamps_timer_begin()
|
||||
void opengl46_local_shadows::gpu_lamps_timer(bool const Begin)
|
||||
{
|
||||
m_lampsframedrawcalls = 0;
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
m_lampstimer.begin();
|
||||
m_lampstiming = true;
|
||||
if (Begin)
|
||||
m_lampstimer.begin();
|
||||
else
|
||||
m_lampstimer.end();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::lamps_timer_end()
|
||||
void opengl46_local_shadows::gpu_begin_headlight(std::size_t const Slot)
|
||||
{
|
||||
if (false == m_lampstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lampstimer.end();
|
||||
m_lampstiming = false;
|
||||
m_lampsdrawcalls = m_lampsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
|
||||
{
|
||||
if (Light.owner == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
auto const &slot{m_slots[idx]};
|
||||
if ((slot.owner == Light.owner) && (slot.part == Light.part))
|
||||
{
|
||||
if (false == slot.scenery)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
Slot = idx;
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::lamp_weights(std::size_t const Slot) const
|
||||
{
|
||||
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
|
||||
return glm::vec4{static_cast<float>(cube), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
|
||||
{
|
||||
m_headlightswanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
auto const size{count > 0 ? setup(level).headlightsize : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
|
||||
{
|
||||
allocate_headlights(count, size);
|
||||
}
|
||||
if (m_headlightslots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// sets which already have a slot keep it
|
||||
std::vector<light_array::light_record const *> unplaced;
|
||||
for (auto const *light : Lights)
|
||||
{
|
||||
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of sets no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = headlight_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.index = light->index;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::reset_headlights()
|
||||
{
|
||||
for (auto &slot : m_headlightslots)
|
||||
{
|
||||
slot = headlight_slot{};
|
||||
}
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_headlightslots[Slot]};
|
||||
auto const level{setup(m_level)};
|
||||
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_headlightslots[Slot]};
|
||||
slot.drawn = true;
|
||||
slot.drawnframe = Framestamp;
|
||||
slot.projector = Projector;
|
||||
slot.rotation = Rotation;
|
||||
slot.projection = Projection;
|
||||
slot.direction = Direction;
|
||||
++m_headlightsdrawn;
|
||||
|
||||
gl::framebuffer::unbind();
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, static_cast<GLint>(Slot));
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_headlightsframebuffer);
|
||||
@@ -649,51 +289,17 @@ void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end_headlight(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_headlight()
|
||||
{
|
||||
gl::framebuffer::unbind();
|
||||
m_headlightsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_begin()
|
||||
void opengl46_local_shadows::gpu_headlights_timer(bool const Begin)
|
||||
{
|
||||
m_headlightsframedrawcalls = 0;
|
||||
m_headlightscpustart = std::chrono::steady_clock::now();
|
||||
m_headlightstimer.begin();
|
||||
m_headlightstiming = true;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_end()
|
||||
{
|
||||
if (false == m_headlightstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_headlightstimer.end();
|
||||
m_headlightstiming = false;
|
||||
m_headlightsdrawcalls = m_headlightsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
|
||||
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
int opengl46_local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
|
||||
{
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
auto const &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == Owner) && (slot.index == Index))
|
||||
{
|
||||
return (slot.drawn ? static_cast<int>(idx) : -1);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::headlight_params(std::size_t const Slot) const
|
||||
{
|
||||
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
|
||||
auto const &projection{m_headlightslots[Slot].projection};
|
||||
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
|
||||
if (Begin)
|
||||
m_headlightstimer.begin();
|
||||
else
|
||||
m_headlightstimer.end();
|
||||
}
|
||||
|
||||
std::string opengl46_local_shadows::info_times()
|
||||
@@ -707,67 +313,3 @@ std::string opengl46_local_shadows::info_times()
|
||||
text += " headlight shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string opengl46_local_shadows::info() const
|
||||
{
|
||||
std::string text;
|
||||
if (m_size != 0)
|
||||
{
|
||||
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
|
||||
" ms";
|
||||
}
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto withvehicles{0};
|
||||
auto ready{0};
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
ready += (m_slots[idx].scenery ? 1 : 0);
|
||||
withvehicles += (m_slots[idx].vehicles ? 1 : 0);
|
||||
}
|
||||
text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " +
|
||||
std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " +
|
||||
std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms";
|
||||
}
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto ready{0};
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
ready += (m_headlightslots[idx].drawn ? 1 : 0);
|
||||
}
|
||||
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
|
||||
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
|
||||
to_string(m_headlightscputime, 2) + " ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::draw_debug_ui()
|
||||
{
|
||||
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
|
||||
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
|
||||
if (m_size != 0)
|
||||
{
|
||||
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
|
||||
}
|
||||
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
|
||||
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);
|
||||
ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f);
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.find("\nscenery lamp shadows:")};
|
||||
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
|
||||
if (line != std::string::npos)
|
||||
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
|
||||
}
|
||||
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
|
||||
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.rfind('\n')};
|
||||
ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,37 +19,17 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/common/locallights.h"
|
||||
#include "rendering/common/localshadows.h"
|
||||
#include "rendering/lightarray.h"
|
||||
|
||||
class TDynamicObject;
|
||||
class TModel3d;
|
||||
|
||||
// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows):
|
||||
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
|
||||
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
|
||||
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
|
||||
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
|
||||
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
|
||||
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
|
||||
// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there
|
||||
// are any by the lamp. the shading reads the working copy then, the scenery part otherwise.
|
||||
// the six faces of a map are drawn at once, by a geometry shader (shadowcube.vert/.geom), so a model costs one draw call per
|
||||
// submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow() in
|
||||
// light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x, from the array of scenery
|
||||
// lamps when its linear is 1 and from the one of the cab lamp otherwise.
|
||||
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
|
||||
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle, see
|
||||
// opengl46_renderer::Headlight_Projector()), with what is within Global.gfx_lights_shadows_headlights_range ahead of the
|
||||
// vehicle, but not the vehicle itself. the map keeps the projector it was drawn with, in world space: one a few frames old is
|
||||
// still right for what doesn't move. read by headlight_beam_shadow() in light_common.glsl, for the headlights with the map in their
|
||||
// headlight_weights.w (1 + the layer) and the projection to it in gl::scene_ubs::headlight_shadow
|
||||
class opengl46_local_shadows
|
||||
// the shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows): the maps and the drawing to them of
|
||||
// gfx::local_shadows, which tells what they hold. the six faces of a cube map are drawn at once, by a geometry shader
|
||||
// (shadowcube.vert/.geom)
|
||||
class opengl46_local_shadows : public gfx::local_shadows
|
||||
{
|
||||
public:
|
||||
opengl46_local_shadows() = default;
|
||||
~opengl46_local_shadows();
|
||||
opengl46_local_shadows(const opengl46_local_shadows &) = delete;
|
||||
opengl46_local_shadows &operator=(const opengl46_local_shadows &) = delete;
|
||||
~opengl46_local_shadows() override;
|
||||
|
||||
// creates the programs and the stand-in maps. throws gl::shader_exception
|
||||
void init();
|
||||
@@ -67,157 +47,26 @@ class opengl46_local_shadows
|
||||
{
|
||||
return Alpha ? m_alpha_multidraw_program.get() : m_multidraw_program.get();
|
||||
}
|
||||
|
||||
// the cab lamp
|
||||
|
||||
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
|
||||
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
|
||||
// the size of the level when the level changed
|
||||
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
|
||||
// sets up the drawing to the map: the framebuffer, viewport, cleared faces, the state of the faces. the geometry is to be
|
||||
// drawn with program() and a modelview from the space of the model to the space of the map
|
||||
void begin();
|
||||
// ends the drawing to the map, leaves the default framebuffer bound and the culling of faces on
|
||||
void end(std::uint32_t const Drawcalls);
|
||||
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
|
||||
bool ready(TDynamicObject const *Vehicle) const
|
||||
{
|
||||
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
|
||||
}
|
||||
// position of the lamp the map was drawn for, in the space of the cab model
|
||||
glm::vec3 const &lamp() const
|
||||
{
|
||||
return m_lamp;
|
||||
}
|
||||
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
|
||||
// radius of the filter in texels
|
||||
glm::vec4 weights() const;
|
||||
|
||||
// lamps of scenery models
|
||||
|
||||
// a lamp with a slot
|
||||
struct lamp_slot
|
||||
{
|
||||
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
|
||||
void const *part{nullptr};
|
||||
glm::dvec3 position{0.0}; // world space
|
||||
float range{0.f}; // of the light, metres
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
|
||||
bool scenery{false}; // the scenery part of the map is drawn
|
||||
std::uint64_t version{0}; // of the scene the scenery part was drawn for
|
||||
bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads
|
||||
unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in
|
||||
};
|
||||
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
|
||||
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
|
||||
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
|
||||
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
|
||||
void reset_lamps();
|
||||
// slots of the lamps picked in this frame
|
||||
std::vector<std::size_t> const &lamps() const
|
||||
{
|
||||
return m_wanted;
|
||||
}
|
||||
lamp_slot const &slot(std::size_t const Slot) const
|
||||
{
|
||||
return m_slots[Slot];
|
||||
}
|
||||
// whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level
|
||||
bool scenery_wanted(std::size_t const Slot);
|
||||
// whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level)
|
||||
bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const;
|
||||
// the slot has no vehicles by it: the shading reads the scenery part
|
||||
void no_vehicles(std::size_t const Slot)
|
||||
{
|
||||
m_slots[Slot].vehicles = false;
|
||||
}
|
||||
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
|
||||
// the geometry is to be drawn with program() and a modelview to world axes centred on the lamp
|
||||
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a lamp
|
||||
void end_lamp(std::uint32_t const Drawcalls);
|
||||
// gpu timer of the drawing of the maps of the lamps in this frame
|
||||
void lamps_timer_begin();
|
||||
void lamps_timer_end();
|
||||
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
|
||||
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
|
||||
// the headlight_weights of the light of the lamp in the slot, see weights()
|
||||
glm::vec4 lamp_weights(std::size_t const Slot) const;
|
||||
|
||||
// headlights of vehicles
|
||||
|
||||
// a set of headlights (one end of a vehicle) with a slot
|
||||
struct headlight_slot
|
||||
{
|
||||
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
|
||||
int index{-1};
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
|
||||
bool drawn{false}; // the map holds the shadows of the headlights
|
||||
unsigned int drawnframe{0};
|
||||
// the projector the map was drawn with: world space position, world axes to its axes, projection
|
||||
glm::dvec3 projector{0.0};
|
||||
glm::mat3 rotation{1.f};
|
||||
glm::mat4 projection{1.f};
|
||||
glm::vec3 direction{0.f}; // of the light when the map was drawn
|
||||
};
|
||||
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
|
||||
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
|
||||
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
|
||||
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
|
||||
void reset_headlights();
|
||||
// slots of the headlights picked in this frame
|
||||
std::vector<std::size_t> const &headlights() const
|
||||
{
|
||||
return m_headlightswanted;
|
||||
}
|
||||
headlight_slot const &headlight(std::size_t const Slot) const
|
||||
{
|
||||
return m_headlightslots[Slot];
|
||||
}
|
||||
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
|
||||
// while the headlights Moved (with the vehicle)
|
||||
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
|
||||
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
|
||||
// to be drawn by the shadow pass with this projection, back faces only
|
||||
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a set of headlights, leaves the default framebuffer bound
|
||||
void end_headlight(std::uint32_t const Drawcalls);
|
||||
// gpu timer of the drawing of the maps of the headlights in this frame
|
||||
void headlights_timer_begin();
|
||||
void headlights_timer_end();
|
||||
// the slot of the headlights with their map ready in this frame, -1 if there is none
|
||||
int headlight_map(TDynamicObject const *Owner, int const Index) const;
|
||||
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
|
||||
glm::vec4 headlight_params(std::size_t const Slot) const;
|
||||
|
||||
// lines for the gpu times and statistics of the debug panel
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
std::string info() const;
|
||||
// controls for the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
protected:
|
||||
bool gpu_allocate(int const Size) override;
|
||||
void gpu_release() override;
|
||||
bool gpu_allocate_lamps(int const Count, int const Size) override;
|
||||
void gpu_release_lamps() override;
|
||||
bool gpu_allocate_headlights(int const Count, int const Size) override;
|
||||
void gpu_release_headlights() override;
|
||||
void gpu_begin_cab() override;
|
||||
void gpu_end_cab() override;
|
||||
void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override;
|
||||
void gpu_end_lamp() override;
|
||||
void gpu_begin_headlight(std::size_t const Slot) override;
|
||||
void gpu_end_headlight() override;
|
||||
void gpu_lamps_timer(bool const Begin) override;
|
||||
void gpu_headlights_timer(bool const Begin) override;
|
||||
|
||||
private:
|
||||
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
|
||||
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
|
||||
// it doesn't) of the maps of headlights
|
||||
struct level_setup
|
||||
{
|
||||
int size;
|
||||
unsigned int interval;
|
||||
float radius;
|
||||
int sceneryperframe;
|
||||
int headlightsize;
|
||||
unsigned int headlightmoving;
|
||||
unsigned int headlightstill;
|
||||
};
|
||||
static level_setup setup(int const Level);
|
||||
void allocate(int const Size);
|
||||
void release();
|
||||
void allocate_lamps(int const Count, int const Size);
|
||||
void release_lamps();
|
||||
void allocate_headlights(int const Count, int const Size);
|
||||
void release_headlights();
|
||||
void set_layer(int const Layer);
|
||||
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
@@ -226,48 +75,16 @@ class opengl46_local_shadows
|
||||
std::unique_ptr<gl::program> m_alpha_multidraw_program;
|
||||
GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth
|
||||
GLuint m_emptyheadlights{0}; // the same, a 2d array of one 1x1 layer
|
||||
int m_level{-1}; // level the maps were made for
|
||||
// cab lamp
|
||||
GLuint m_texture{0}; // cube map array, one cube
|
||||
// cab lamp: cube map array, one cube
|
||||
GLuint m_texture{0};
|
||||
GLuint m_framebuffer{0};
|
||||
int m_size{0}; // of a face of m_texture, 0 when there is none
|
||||
bool m_valid{false}; // m_texture holds the map of m_vehicle
|
||||
TDynamicObject const *m_vehicle{nullptr};
|
||||
TModel3d const *m_cab{nullptr};
|
||||
glm::vec3 m_lamp{0.f};
|
||||
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
|
||||
gl::gpu_timer m_timer;
|
||||
// scenery lamps: cube map array, the scenery parts of the slots first, then their working copies
|
||||
GLuint m_lampstexture{0};
|
||||
GLuint m_lampsframebuffer{0};
|
||||
int m_lampssize{0};
|
||||
std::vector<lamp_slot> m_slots;
|
||||
std::vector<std::size_t> m_wanted;
|
||||
unsigned int m_frame{0}; // framestamp of the last update_lamps()
|
||||
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
|
||||
gl::gpu_timer m_lampstimer;
|
||||
bool m_lampstiming{false};
|
||||
// headlights: 2d array, a layer per slot
|
||||
GLuint m_headlightstexture{0};
|
||||
GLuint m_headlightsframebuffer{0};
|
||||
int m_headlightssize{0};
|
||||
std::vector<headlight_slot> m_headlightslots;
|
||||
std::vector<std::size_t> m_headlightswanted;
|
||||
gl::gpu_timer m_headlightstimer;
|
||||
bool m_headlightstiming{false};
|
||||
std::chrono::steady_clock::time_point m_headlightscpustart;
|
||||
// statistics
|
||||
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
||||
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
|
||||
double m_cputime{0.0}; // milliseconds, smoothed
|
||||
std::chrono::steady_clock::time_point m_cpustart;
|
||||
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far
|
||||
std::uint32_t m_lampsvehicles{0}; // working copies drawn so far
|
||||
std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
|
||||
std::uint32_t m_lampsframedrawcalls{0};
|
||||
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
|
||||
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
|
||||
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
|
||||
std::uint32_t m_headlightsframedrawcalls{0};
|
||||
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -216,14 +216,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// the gpu culling of the instances, models and sleepers, the depth pass of the whole scene
|
||||
void walk_region_begin() override;
|
||||
void walk_editor_cursor() override;
|
||||
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area) override;
|
||||
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) override;
|
||||
bool walk_queue_model(TAnimModel *Instance, bool const Inrange) override;
|
||||
bool walk_queue_sleepers(TTrack *Track) override;
|
||||
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) override;
|
||||
bool walk_instances_begin() override;
|
||||
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) override;
|
||||
void walk_instances_end() override;
|
||||
gfx::geometrybank_manager &walk_geometry() override;
|
||||
bool walk_blending_enabled() const override;
|
||||
|
||||
// types
|
||||
struct viewport_config {
|
||||
@@ -346,11 +342,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// materials like the main view. no occlusion test, no depth prepass, one phase
|
||||
void Render_Instances_Indirect_Pass();
|
||||
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
|
||||
// (m_indirectdraws, m_indirectcommands), puts them in order for multi draws and has Cull fill in their commands
|
||||
// (m_indirectdraws, m_indirectcommands; Walk_Indirect_Draws()), puts them in order for multi draws and has Cull fill in their
|
||||
// commands
|
||||
void Record_Indirect_Draws( opengl46_instance_cull &Cull );
|
||||
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
|
||||
// is kept for the rest of the pass
|
||||
bool Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket );
|
||||
// the culling the pass under way feeds (Begin_Pass_Cull()), always one of this renderer
|
||||
opengl46_instance_cull &Pass_Cull() { return static_cast<opengl46_instance_cull &>( *m_passcull ); }
|
||||
// draws of the instances made, and the gl calls which made them
|
||||
struct indirect_counts {
|
||||
std::uint32_t draws { 0 };
|
||||
@@ -369,18 +365,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
|
||||
// the recorded draws of a shadow pass with the shadow programs, the way Bind_Material_Shadow() picks them
|
||||
indirect_counts Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set );
|
||||
// the texture Bind_Material_Shadow() binds for the alpha test of a material, or null_handle (no test)
|
||||
texture_handle Indirect_Shadow_Texture( material_handle const Material );
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
|
||||
// same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in
|
||||
// the buffer of the frame (m_drawblockstream), in the same order
|
||||
void Order_Indirect_Draws();
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw), put in order by Order_Indirect_Draws(): places their
|
||||
// model blocks in the buffer of the frame (m_drawblockstream), in the same order
|
||||
void Upload_Indirect_Blocks();
|
||||
// binds the model blocks of Count recorded draws from First for the programs of the multi draws (MODEL_DRAW_SSBO_BINDING)
|
||||
void Bind_Indirect_Blocks( std::size_t const First, std::size_t const Count );
|
||||
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
|
||||
// bank, primitive and blending
|
||||
bool Indirect_Mergeable( std::size_t const First, std::size_t const Other ) const;
|
||||
// the program Bind_Material() binds for the material, nullptr when it binds none
|
||||
gl::program const *Indirect_Program( material_handle Material ) const;
|
||||
// the variant of a material program for multi draws (built with MODEL_DRAWS), made the first time it's asked for.
|
||||
@@ -431,14 +420,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas );
|
||||
// whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry
|
||||
bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
|
||||
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
|
||||
// Render_Sleepers(), making their records first when they're missing or out of date
|
||||
void Queue_Sleepers( TTrack *Track );
|
||||
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
|
||||
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
|
||||
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
|
||||
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
|
||||
bool Queue_Model( TAnimModel *Instance, bool const Inrange );
|
||||
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
|
||||
void Update_Texture_Release();
|
||||
// textures used by materials of the sub-models of specified model, replaceable skins excluded
|
||||
@@ -566,7 +547,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared
|
||||
// with the one of the cpu (2)
|
||||
opengl46_instance_cull m_instancecull;
|
||||
bool m_instancecullpass { false }; // the current pass feeds a culling: m_passcull
|
||||
unsigned int m_instancecullframe { ~0u }; // frame it last ran in
|
||||
// the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows,
|
||||
// _reflections), with records and programs shared with m_instancecull
|
||||
@@ -578,42 +558,8 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::vector<std::unique_ptr<opengl46_instance_cull>> m_lampcull;
|
||||
opengl46_instance_cull *m_lightshadowcull { nullptr }; // the one of the map of headlights or of a lamp being drawn
|
||||
std::size_t m_lampscenerydrawn { 0 }; // scenery parts of the maps of lamps drawn in this frame so far
|
||||
opengl46_instance_cull *m_passcull { nullptr }; // the one the current pass feeds
|
||||
opengl46_instance_cull *m_indirectcull { &m_instancecull }; // the one the recorded draws (m_indirectdraws) were culled by
|
||||
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
|
||||
// Instances_Cast_Shadow() of the groups of the shadow pass under way
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
|
||||
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
|
||||
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
|
||||
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
|
||||
struct sleeper_records {
|
||||
scene::basic_cell::instance_bucket bucket; // the records only, no instances
|
||||
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
|
||||
scene::bounding_area area; // of the locations of the sleepers
|
||||
TModel3d const *model { nullptr }; // what the records were made from
|
||||
material_handle skin { null_handle };
|
||||
glm::mat4 const *transforms { nullptr };
|
||||
std::size_t count { 0 };
|
||||
unsigned int frame { 0 }; // queued last
|
||||
};
|
||||
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
|
||||
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
|
||||
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
|
||||
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
|
||||
struct model_records {
|
||||
scene::basic_cell::instance_bucket bucket; // the instance alone
|
||||
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
|
||||
scene::bounding_area area; // the location of the instance
|
||||
unsigned int frame { 0 }; // queued last
|
||||
};
|
||||
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
|
||||
bool m_modelspass { false }; // the models drawn on their own of the pass under way go to its gpu culling
|
||||
bool m_indirectcolourmodels { false }; // with m_indirectcolourpending: the depth pass culled and recorded them too
|
||||
bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
|
||||
bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
|
||||
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
|
||||
// drawn before the culling runs)
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
|
||||
// depth pyramid for the occlusion test of the gpu culling
|
||||
opengl46_hiz m_hiz;
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (Global.gfx_shadow_sdsm)
|
||||
@@ -621,75 +567,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// with a cascade fitted: the corners of the area its map is to cover, for setup_pass()
|
||||
std::array<glm::vec3, 8> const *m_shadowfitarea { nullptr };
|
||||
// what the last shadow pass did, for the debug panel
|
||||
struct shadow_fit_state {
|
||||
bool fitted { false };
|
||||
char const *reason { nullptr }; // why not, with gfx.shadow.sdsm on
|
||||
unsigned int age { 0 }; // frames since the measurement
|
||||
float depthmin { 0.f };
|
||||
float depthmax { 0.f };
|
||||
float turn { 0.f }; // of the view since the measurement, degrees
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits {};
|
||||
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
|
||||
std::array<float, gl::MAX_CASCADES> slice {};
|
||||
std::array<float, gl::MAX_CASCADES> drawn {};
|
||||
} m_shadowfitstate;
|
||||
gfx::shadow_fit::fit_state m_shadowfitstate;
|
||||
// the sizes setup_pass() found for the last shadow pass: of the whole slice of the view and as drawn
|
||||
float m_shadowslicesize { 0.f };
|
||||
float m_shadowdrawnsize { 0.f };
|
||||
// a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be
|
||||
// replayed once the instances of its command are known: the state the walk left for it and what it bound
|
||||
struct indirect_draw {
|
||||
gl::model_ubs ubs;
|
||||
material_handle material;
|
||||
TSubModel const *submodel;
|
||||
gfx::geometry_handle geometry;
|
||||
unsigned int type; // primitive
|
||||
bool blending; // drawn with the transparency hack of submodels faded out
|
||||
std::uint32_t group { 0 }; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group())
|
||||
};
|
||||
std::vector<indirect_draw> m_indirectdraws;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
|
||||
// the walks Record_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the
|
||||
// draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it
|
||||
struct cascade_band {
|
||||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
struct cascade_record {
|
||||
unsigned int frame { 0 };
|
||||
std::vector<cascade_band> bands;
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
|
||||
std::vector<indirect_draw> m_cascadedraws;
|
||||
std::vector<gl::instance_draw_command> m_cascadecommands;
|
||||
unsigned int m_cascaderecordframe { 0 };
|
||||
// the walks Record_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
|
||||
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
|
||||
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
|
||||
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
|
||||
// own (with an instance in the key) are walked every time
|
||||
struct walk_band {
|
||||
std::vector<indirect_draw> draws; // as recorded
|
||||
float bandmin { 0.f };
|
||||
std::uint32_t modelversion { 0 };
|
||||
bool blending { false }; // the state the walk began in
|
||||
bool valid { false };
|
||||
};
|
||||
struct walk_record {
|
||||
unsigned int frame { 0 }; // used last
|
||||
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
|
||||
int m_walkrankcutoff { 0 }; // Global.gfx_shadow_rank_cutoff the walks were made with
|
||||
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
|
||||
// of the walks and of putting the draws in order with their blocks uploaded
|
||||
struct walk_counts {
|
||||
std::uint32_t walked { 0 };
|
||||
std::uint32_t changing { 0 };
|
||||
std::uint32_t reused { 0 };
|
||||
double walktime { 0.0 };
|
||||
double ordertime { 0.0 };
|
||||
};
|
||||
// cpu time of the parts of the culling and the draws of the instances of the main view (Render_Instances_Indirect() and
|
||||
// _Pending()), for the panel: the depth of the recorded walk of the scene drawn before the culling, the depth pyramids, the
|
||||
// culling passes, the walks with putting the draws in order, the depth and the colour of the instances
|
||||
@@ -702,47 +583,13 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
double colour { 0.0 };
|
||||
};
|
||||
cull_parts m_cullparts;
|
||||
walk_counts m_walkcounts;
|
||||
walk_counts m_lastwalkcounts;
|
||||
unsigned int m_walkcountsframe { 0 };
|
||||
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
|
||||
void Replay_Walk( walk_band const &Walk, bool const Shadows );
|
||||
// multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where
|
||||
// the blocks are
|
||||
bool m_indirectmultidraw { false };
|
||||
GLuint m_indirectblockbuffer { 0 };
|
||||
GLintptr m_indirectblockoffset { 0 };
|
||||
std::uint64_t m_indirectblockgeneration { 0 };
|
||||
// working space of Order_Indirect_Draws() and Upload_Indirect_Blocks(), kept between frames
|
||||
struct indirect_queue {
|
||||
std::uint32_t next; // draw
|
||||
std::uint32_t end;
|
||||
std::uint32_t link; // next queue waiting for the same state
|
||||
};
|
||||
struct indirect_state {
|
||||
std::uint32_t head;
|
||||
std::uint32_t tail;
|
||||
};
|
||||
// what a multi draw has in common, and the draw which has it
|
||||
struct indirect_state_key {
|
||||
material_handle material;
|
||||
std::uint32_t variant; // of the textures of the material
|
||||
std::uint32_t bank;
|
||||
unsigned int type;
|
||||
std::uint32_t indexed;
|
||||
bool blending;
|
||||
std::uint32_t draw;
|
||||
auto tie() const {
|
||||
return std::tie( material, variant, bank, type, indexed, blending ); }
|
||||
};
|
||||
std::vector<indirect_state_key> m_indirectstatekeys;
|
||||
std::vector<std::uint32_t> m_indirectdrawstates;
|
||||
std::vector<indirect_queue> m_indirectqueues;
|
||||
std::vector<indirect_state> m_indirectstates;
|
||||
std::vector<std::uint32_t> m_indirectstateorder;
|
||||
std::vector<std::uint32_t> m_indirectorder;
|
||||
std::vector<indirect_draw> m_indirectdrawsordered;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
|
||||
// working space of Upload_Indirect_Blocks(), kept between frames
|
||||
std::vector<gl::model_ubs> m_indirectblocks;
|
||||
// the model blocks of the multi draws, one buffer for each of the last frames like model_ubo (nullptr: no multi draws)
|
||||
std::unique_ptr<gl::frame_stream> m_drawblockstream;
|
||||
@@ -754,10 +601,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::unique_ptr<gl::program> m_shadow_multidraw_shader; // and of the sun shadows
|
||||
std::unique_ptr<gl::program> m_alpha_shadow_multidraw_shader;
|
||||
bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws
|
||||
bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing
|
||||
std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded
|
||||
material_handle m_indirectmaterial { null_handle }; // what the last Bind_Material() bound while recording
|
||||
TSubModel const *m_indirectsubmodel { nullptr };
|
||||
// depth prepass: programs and the class of the material programs (see Prepass_Class())
|
||||
std::unique_ptr<gl::program> m_prepass_shader;
|
||||
std::unique_ptr<gl::program> m_prepass_alpha_shader;
|
||||
@@ -768,7 +611,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
bool m_scenedepthpass { false }; // inside it: Bind_Material() and draw() work for the depth
|
||||
bool m_scenedepthskip { true }; // the material bound last isn't drawn in it
|
||||
bool m_scenedepthdrawn { false }; // it was drawn for the colour pass under way, which skips what was done in it already
|
||||
bool m_indirectcolourpending { false }; // the instances were culled in it, the colour pass draws them from the recorded draws
|
||||
// with m_indirectcolourpending: the instances were culled in it, the colour pass draws them from the recorded draws
|
||||
opengl46_instance_cull::command_set m_indirectcolourset { opengl46_instance_cull::command_set::first }; // with these commands
|
||||
debug_stats m_scenedepthstats; // of its last run
|
||||
double m_scenedepthcputime { 0.0 }; // ms
|
||||
@@ -894,12 +737,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
opengl46_local_shadows m_localshadows;
|
||||
// the shadow pass draws to a cube shadow map, with the programs of m_localshadows
|
||||
bool m_cubeshadows = false;
|
||||
// vehicles near the camera which cast the shadows of scenery lamps, and those of them by the lamp being drawn
|
||||
std::vector<TDynamicObject *> m_lampshadowvehicles;
|
||||
std::vector<TDynamicObject *> m_lampshadowslot;
|
||||
// headlights which may get shadow maps in this frame, and their importance
|
||||
std::vector<std::pair<float, light_array::light_record const *>> m_headlightshadowcandidates;
|
||||
std::vector<light_array::light_record const *> m_headlightshadowpicked;
|
||||
|
||||
double m_precipitationrotation;
|
||||
|
||||
|
||||
@@ -10,27 +10,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46shadowfit.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
|
||||
// get any closer to the camera than this, however close the pixels are
|
||||
float const SPLIT_NEAR_MIN{0.5f};
|
||||
float const SPLIT_FAR_MIN{16.f};
|
||||
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
|
||||
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
|
||||
// has to be within its map
|
||||
float const MARGIN_METRES{1.f};
|
||||
float const MARGIN_SHARE{0.05f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_shadow_fit::~opengl46_shadow_fit()
|
||||
{
|
||||
release();
|
||||
@@ -97,20 +80,7 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
depth = m_depth;
|
||||
}
|
||||
|
||||
// numbers start from 1, 0 is no measurement
|
||||
if (++m_measurement == 0)
|
||||
++m_measurement;
|
||||
gl::shadow_fit_data data{};
|
||||
data.measurement = m_measurement;
|
||||
data.depthmin = std::numeric_limits<float>::infinity();
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
for (std::size_t axis = 0; axis < 4; ++axis)
|
||||
{
|
||||
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
|
||||
data.bounds[cascade * 8 + 4 + axis] = 0u;
|
||||
}
|
||||
}
|
||||
auto const data{begin_measurement(View, Frame)};
|
||||
m_buffer->update(data);
|
||||
m_buffer->bind_base();
|
||||
|
||||
@@ -124,11 +94,12 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
m_depthprogram->dispatch(groupsx, groupsy);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
|
||||
auto const bounds{bounds_params(View)};
|
||||
auto const margins{margin_params()};
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 0, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 4, 1, GL_FALSE, glm::value_ptr(viewtolight));
|
||||
glProgramUniform4f(*m_boundsprogram, 8, View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f));
|
||||
glProgramUniform4f(*m_boundsprogram, 9, MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f);
|
||||
glProgramUniform4f(*m_boundsprogram, 8, bounds.x, bounds.y, bounds.z, bounds.w);
|
||||
glProgramUniform4f(*m_boundsprogram, 9, margins.x, margins.y, margins.z, margins.w);
|
||||
m_boundsprogram->dispatch(groupsx, groupsy);
|
||||
// for the copy of the read back
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
@@ -139,27 +110,14 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
m_timer.end();
|
||||
|
||||
m_readback->push(*m_buffer);
|
||||
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
|
||||
}
|
||||
|
||||
opengl46_shadow_fit::result const *opengl46_shadow_fit::latest()
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
if (m_readback && m_readback->pop(&data))
|
||||
{
|
||||
auto const &source{m_pending[data.measurement % PENDING]};
|
||||
if ((data.measurement != 0) && (source.measurement == data.measurement))
|
||||
m_latest = result{data, source.source, source.frame};
|
||||
}
|
||||
return m_latest ? &(*m_latest) : nullptr;
|
||||
}
|
||||
|
||||
float opengl46_shadow_fit::unorder(std::uint32_t const Value)
|
||||
{
|
||||
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
|
||||
float result;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
arrived(data);
|
||||
return gfx::shadow_fit::latest();
|
||||
}
|
||||
|
||||
std::string opengl46_shadow_fit::info_times()
|
||||
|
||||
@@ -19,34 +19,14 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/common/shadowfit.h"
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps, gfx.shadow.sdsm).
|
||||
// at the end of the colour pass of the main view two compute passes go over its resolved depth: the nearest and the farthest
|
||||
// distance of the pixels which aren't the sky (shadow_fit_depth.comp), then the distances split between the cascades and,
|
||||
// for each cascade, the box of the pixels it takes in the space of the light (shadow_fit_bounds.comp). the result comes back
|
||||
// to the cpu a frame or two later without waiting (gl::readback), with the camera and the light it was measured with, and
|
||||
// the renderer makes the cascades of the frame cover the boxes (opengl46_renderer::Shadow_Fit()) instead of the whole slices
|
||||
// of the view between the fixed distances of shadowtune
|
||||
class opengl46_shadow_fit
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm): the passes and the buffers of
|
||||
// gfx::shadow_fit, which tells what they do. the result comes back to the cpu a frame or two later without waiting
|
||||
// (gl::readback), and opengl46_renderer::Shadow_Fit() fits the cascades of the frame to it
|
||||
class opengl46_shadow_fit : public gfx::shadow_fit
|
||||
{
|
||||
public:
|
||||
// the view and the light a measurement was made with
|
||||
struct view
|
||||
{
|
||||
glm::dvec3 camera; // position of the camera
|
||||
glm::mat4 projection; // projection of the view
|
||||
glm::mat3 rotation; // world to view space, rotation only
|
||||
glm::mat3 light; // world to the space of the light, rotation only
|
||||
float range{0.f}; // the farthest distance the cascades may reach
|
||||
};
|
||||
// a measurement which came back
|
||||
struct result
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
view source;
|
||||
unsigned int frame{0}; // frame of the renderer it was made in
|
||||
};
|
||||
|
||||
opengl46_shadow_fit() = default;
|
||||
~opengl46_shadow_fit();
|
||||
opengl46_shadow_fit(const opengl46_shadow_fit &) = delete;
|
||||
@@ -63,30 +43,15 @@ class opengl46_shadow_fit
|
||||
// the time of the passes, for the debug panel
|
||||
std::string info_times();
|
||||
|
||||
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
|
||||
static float unorder(std::uint32_t const Value);
|
||||
|
||||
private:
|
||||
// the texture the depth is resolved to when there is no resolved depth to measure
|
||||
void allocate(int const Width, int const Height);
|
||||
void release();
|
||||
|
||||
// a measurement on its way back, kept by its number
|
||||
struct pending
|
||||
{
|
||||
std::uint32_t measurement{0}; // 0: none
|
||||
view source;
|
||||
unsigned int frame{0};
|
||||
};
|
||||
static constexpr std::size_t PENDING = 8; // more than the copies gl::readback keeps in flight
|
||||
|
||||
std::unique_ptr<gl::program> m_depthprogram;
|
||||
std::unique_ptr<gl::program> m_boundsprogram;
|
||||
std::unique_ptr<gl::ssbo> m_buffer;
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
std::array<pending, PENDING> m_pending;
|
||||
std::uint32_t m_measurement{0}; // number of the last measurement
|
||||
std::optional<result> m_latest;
|
||||
GLuint m_depth{0};
|
||||
GLuint m_depthfb{0};
|
||||
glm::ivec2 m_depthsize{0};
|
||||
|
||||
@@ -12,84 +12,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "environment/skydome.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
|
||||
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
|
||||
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
|
||||
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
|
||||
float const SH_Y00{0.282095f};
|
||||
float const SH_Y1{0.488603f};
|
||||
float const PI{3.14159265359f};
|
||||
|
||||
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
|
||||
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
|
||||
{
|
||||
return {SH_Y00,
|
||||
SH_Y1 * n.y,
|
||||
SH_Y1 * n.z,
|
||||
SH_Y1 * n.x,
|
||||
1.092548f * n.x * n.y,
|
||||
1.092548f * n.y * n.z,
|
||||
0.315392f * (3.f * n.z * n.z - 1.f),
|
||||
1.092548f * n.x * n.z,
|
||||
0.546274f * (n.x * n.x - n.y * n.y)};
|
||||
}
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
|
||||
{
|
||||
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
|
||||
}
|
||||
|
||||
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
|
||||
glm::vec3 sky_shoulder(glm::vec3 const &Color)
|
||||
{
|
||||
float const knee{0.25f};
|
||||
float const range{0.95f - knee};
|
||||
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
|
||||
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
|
||||
}
|
||||
|
||||
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
|
||||
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
|
||||
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
|
||||
// renderer, see the notes of the plan, stage S2)
|
||||
float const ATMOSPHERE_SKY_SCALE{16.f};
|
||||
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
|
||||
float const ATMOSPHERE_SUN_SCALE{1.45f};
|
||||
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
|
||||
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
|
||||
float const ATMOSPHERE_FADE_START{-6.f};
|
||||
float const ATMOSPHERE_FADE_END{10.f};
|
||||
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
|
||||
// ground, in the units of the ambient light, is the diffuse colour times their ratio
|
||||
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
|
||||
|
||||
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
|
||||
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
|
||||
float const RAYLEIGH_SCALE_HEIGHT{8.f};
|
||||
float const MIE_SCATTERING{3.996e-3f};
|
||||
float const MIE_EXTINCTION{4.440e-3f};
|
||||
float const MIE_SCALE_HEIGHT{1.2f};
|
||||
float const MIE_ASYMMETRY{0.8f};
|
||||
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
|
||||
float const OZONE_ALTITUDE{25.f};
|
||||
float const OZONE_HALF_WIDTH{15.f};
|
||||
float const BOTTOM_RADIUS{6360.f};
|
||||
float const TOP_RADIUS{6460.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_sky::~opengl46_sky()
|
||||
{
|
||||
@@ -137,15 +63,7 @@ void opengl46_sky::init()
|
||||
m_skyview_table = create_table(gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
|
||||
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
|
||||
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
|
||||
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
|
||||
m_atmosphere.top_radius = TOP_RADIUS;
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->update(m_light.atmosphere());
|
||||
}
|
||||
|
||||
void opengl46_sky::bind()
|
||||
@@ -155,75 +73,13 @@ void opengl46_sky::bind()
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
}
|
||||
|
||||
opengl46_sky::sh_coefficients opengl46_sky::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
|
||||
{
|
||||
sh_coefficients radiance{};
|
||||
auto const &vertices{Skydome.vertices()};
|
||||
auto const &colors{Skydome.colors()};
|
||||
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
|
||||
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
|
||||
float totalweight{0.f};
|
||||
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
|
||||
{
|
||||
auto direction{glm::normalize(vertices[i])};
|
||||
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
|
||||
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
|
||||
// both are undone here, so the light comes from the sky the Perez model describes
|
||||
auto const height{std::max(direction.y, 0.05f)};
|
||||
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
|
||||
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
|
||||
direction.y = std::max(direction.y, 0.f);
|
||||
direction = glm::normalize(direction);
|
||||
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
|
||||
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
|
||||
auto const basis{sh_basis(direction)};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
radiance[c] += color * (basis[c] * weight);
|
||||
totalweight += weight;
|
||||
}
|
||||
if (totalweight <= 0.f)
|
||||
return {};
|
||||
for (auto &coefficient : radiance)
|
||||
coefficient *= 2.f * PI / totalweight;
|
||||
|
||||
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
|
||||
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
|
||||
// which converts the sunlight to them
|
||||
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
|
||||
glm::vec3 skyup{0.f};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
|
||||
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
|
||||
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
|
||||
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
|
||||
radiance[1] += groundradiance * (SH_Y1 * -PI);
|
||||
|
||||
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
|
||||
sh_coefficients directional{};
|
||||
glm::vec3 const average{radiance[0] * SH_Y00};
|
||||
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
|
||||
for (std::size_t c = 1; c < radiance.size(); ++c)
|
||||
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
|
||||
return directional;
|
||||
}
|
||||
|
||||
void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
{
|
||||
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
|
||||
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
|
||||
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
|
||||
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
|
||||
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
|
||||
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
|
||||
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
|
||||
if (!mediumchanged && !sunchanged)
|
||||
bool mediumchanged{false};
|
||||
if (false == m_light.atmosphere_changed(Sundirection, mediumchanged))
|
||||
return;
|
||||
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->update(m_light.atmosphere());
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
|
||||
@@ -257,89 +113,23 @@ void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_result_readback.push(*m_atmosphere_result_ssbo);
|
||||
m_tables_medium = medium;
|
||||
m_tables_sun = Sundirection;
|
||||
}
|
||||
|
||||
void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
|
||||
{
|
||||
// the light the simulation set, which is kept where the atmosphere is not used
|
||||
glm::vec3 const oldambient{Sunlight.ambient};
|
||||
glm::vec3 const olddiffuse{Sunlight.diffuse};
|
||||
glm::vec3 const oldfog{Fogcolor};
|
||||
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
|
||||
|
||||
m_light.begin_update();
|
||||
// atmosphere
|
||||
m_weight = 0.f;
|
||||
if (Global.gfx_atmosphere)
|
||||
{
|
||||
update_atmosphere(glm::normalize(Sundirection));
|
||||
gl::atmosphere_result result;
|
||||
if (m_result_readback.pop(&result))
|
||||
m_result = result;
|
||||
if (m_result)
|
||||
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
|
||||
}
|
||||
|
||||
sh_coefficients directional{};
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
// the dome changes with the sun, the weather and the light the simulation works out from them
|
||||
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
|
||||
if (!m_dome_key || *m_dome_key != key)
|
||||
{
|
||||
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
|
||||
m_dome_key = key;
|
||||
}
|
||||
directional = m_dome_sh;
|
||||
}
|
||||
|
||||
m_sunlight = olddiffuse;
|
||||
m_ambient = oldambient;
|
||||
m_fog = oldfog;
|
||||
m_sun_disc_color = glm::vec3{1.f};
|
||||
if (m_weight > 0.f)
|
||||
{
|
||||
auto const &result{*m_result};
|
||||
// the camera's white balance: the sun high in the sky is white
|
||||
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
|
||||
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
|
||||
|
||||
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
|
||||
// with the shadows of the clouds the sun is held back where they are, not everywhere
|
||||
auto const overcastsun{m_cloud_shadows ? 1.f : (1.f - 0.65f * m_overcast)};
|
||||
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * overcastsun};
|
||||
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
|
||||
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
|
||||
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
|
||||
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
|
||||
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
|
||||
|
||||
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
|
||||
m_ambient = glm::mix(oldambient, ambient, m_weight);
|
||||
m_fog = glm::mix(oldfog, fog, m_weight);
|
||||
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
|
||||
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
|
||||
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
auto const evenness{1.f - 0.75f * m_overcast};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
{
|
||||
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
|
||||
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
|
||||
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
|
||||
Fogcolor = m_fog;
|
||||
m_light.set_result(result);
|
||||
}
|
||||
m_light.end_update(Sunlight, Fogcolor, Skydome, Sundirection, Sunangle);
|
||||
|
||||
// directional part of the ambient light for the shading
|
||||
gl::sky_data skydata{};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
|
||||
auto const &skydata{m_light.sky_data()};
|
||||
if (!m_sky_data_uploaded || std::memcmp(&skydata, &m_sky_data, sizeof(skydata)) != 0)
|
||||
{
|
||||
m_sky_data = skydata;
|
||||
@@ -351,14 +141,12 @@ void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDom
|
||||
void opengl46_sky::setup_sky(gl::model_ubs &Model) const
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, m_skyview_table);
|
||||
// the volumetric clouds make the overcast sky themselves
|
||||
Model.param[0] = glm::vec4{m_weight, m_sky_scale, m_clouds ? 0.f : 0.75f * m_overcast, m_sky_flat_luminance};
|
||||
Model.param[1] = glm::vec4{m_white_balance, 0.f};
|
||||
m_light.sky_params(Model.param[0], Model.param[1]);
|
||||
}
|
||||
|
||||
glm::vec3 opengl46_sky::sun_disc_color(glm::vec3 const &Oldcolor) const
|
||||
{
|
||||
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
|
||||
return m_light.sun_disc_color(Oldcolor);
|
||||
}
|
||||
|
||||
void opengl46_sky::prefilter(gl::cubemap const &Source)
|
||||
@@ -402,19 +190,5 @@ std::string opengl46_sky::info_times()
|
||||
|
||||
void opengl46_sky::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
|
||||
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
|
||||
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
|
||||
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
|
||||
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
|
||||
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
|
||||
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
|
||||
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
|
||||
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
|
||||
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
|
||||
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
|
||||
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
|
||||
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
|
||||
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
|
||||
m_light.draw_debug_ui();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
#include "rendering/common/skylight.h"
|
||||
|
||||
class CSkyDome;
|
||||
|
||||
@@ -32,7 +33,8 @@ class CSkyDome;
|
||||
// them (sky_atmosphere.frag), and the sunlight, ambient light and fog colour read back from them a few frames later.
|
||||
// these are values for the render only: the simulation (Global.DayLight, fLuminance) keeps its own
|
||||
// - the environment map prefiltered with the GGX lobe, for rough reflections (envmap_prefilter.comp)
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before.
|
||||
// what isn't the work of the gpu is the light of the sky shared with the vulkan renderer (gfx::sky_light)
|
||||
class opengl46_sky
|
||||
{
|
||||
public:
|
||||
@@ -52,7 +54,7 @@ class opengl46_sky
|
||||
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
|
||||
float atmosphere_weight() const
|
||||
{
|
||||
return m_weight;
|
||||
return m_light.atmosphere_weight();
|
||||
}
|
||||
// the sky-view table of the atmosphere, its white balance, brightness and the overcast it was drawn with, for the colours of
|
||||
// the fog (opengl46_weather)
|
||||
@@ -62,22 +64,21 @@ class opengl46_sky
|
||||
}
|
||||
glm::vec3 const &white_balance() const
|
||||
{
|
||||
return m_white_balance;
|
||||
return m_light.white_balance();
|
||||
}
|
||||
float sky_scale() const
|
||||
{
|
||||
return m_sky_scale;
|
||||
return m_light.sky_scale();
|
||||
}
|
||||
float overcast() const
|
||||
{
|
||||
return m_overcast;
|
||||
return m_light.overcast();
|
||||
}
|
||||
// the volumetric clouds are drawn over the sky (it doesn't take the overcast look then), and their shadows hold back the
|
||||
// sunlight (it isn't dimmed by the overcast then)
|
||||
void set_clouds(bool const Clouds, bool const Shadows)
|
||||
{
|
||||
m_clouds = Clouds;
|
||||
m_cloud_shadows = Shadows;
|
||||
m_light.set_clouds(Clouds, Shadows);
|
||||
}
|
||||
// sets the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag) and binds its table
|
||||
void setup_sky(gl::model_ubs &Model) const;
|
||||
@@ -96,11 +97,6 @@ class opengl46_sky
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
// spherical harmonics of order 2, rgb
|
||||
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
|
||||
|
||||
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
|
||||
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
|
||||
// sets up the parameters of the atmosphere for the frame and runs the passes whose input changed
|
||||
void update_atmosphere(glm::vec3 const &Sundirection);
|
||||
// creates an immutable 2d texture for a look-up table
|
||||
@@ -121,31 +117,11 @@ class opengl46_sky
|
||||
GLuint m_transmittance_table{0};
|
||||
GLuint m_multiscattering_table{0};
|
||||
GLuint m_skyview_table{0};
|
||||
gl::atmosphere_ubs m_atmosphere{};
|
||||
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
|
||||
// the sky-view table also on the sun
|
||||
std::optional<glm::vec4> m_tables_medium;
|
||||
std::optional<glm::vec3> m_tables_sun;
|
||||
gl::readback m_result_readback{sizeof(gl::atmosphere_result)};
|
||||
std::optional<gl::atmosphere_result> m_result;
|
||||
gl::gpu_timer m_atmosphere_timer;
|
||||
|
||||
// what update() made of the frame, for the sky, the sun disc and the debug window
|
||||
float m_weight{0.f};
|
||||
glm::vec3 m_white_balance{1.f};
|
||||
float m_sky_scale{0.f};
|
||||
float m_overcast{0.f};
|
||||
bool m_clouds{false};
|
||||
bool m_cloud_shadows{false};
|
||||
float m_sky_flat_luminance{0.f};
|
||||
glm::vec3 m_sun_disc_color{1.f};
|
||||
glm::vec3 m_sunlight{0.f};
|
||||
glm::vec3 m_ambient{0.f};
|
||||
glm::vec3 m_fog{0.f};
|
||||
|
||||
// cache of the directional ambient light of the dome, remade when its inputs change
|
||||
std::optional<std::array<glm::vec4, 3>> m_dome_key;
|
||||
sh_coefficients m_dome_sh{};
|
||||
// the light of the sky, shared with the vulkan renderer
|
||||
gfx::sky_light m_light;
|
||||
|
||||
// prefiltered environment map
|
||||
std::unique_ptr<gl::program> m_prefilter_program;
|
||||
|
||||
@@ -17,13 +17,6 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr initial_capacity{8};
|
||||
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
|
||||
float constexpr default_repeat{8.f};
|
||||
|
||||
// array textures: diffuse, normal maps, specular/gloss maps
|
||||
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
|
||||
|
||||
void bind_for_upload(opengl_texture const &Texture)
|
||||
{
|
||||
::glActiveTexture(GL_TEXTURE0);
|
||||
@@ -38,11 +31,8 @@ void set_anisotropy(GLenum const Target)
|
||||
|
||||
} // namespace
|
||||
|
||||
bool opengl46_terrain_materials::init()
|
||||
bool opengl46_terrain_materials::gpu_init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
try
|
||||
{
|
||||
m_copy = std::make_unique<gl::postfx>(gl::shader("copy.frag"));
|
||||
@@ -53,151 +43,56 @@ bool opengl46_terrain_materials::init()
|
||||
return false;
|
||||
}
|
||||
m_framebuffer = std::make_unique<gl::framebuffer>();
|
||||
|
||||
m_white = make_texture("internal_src:terrain/white", GL_TEXTURE_2D);
|
||||
{
|
||||
auto const &white{m_textures.texture(m_white)};
|
||||
std::uint8_t const texel[4]{255, 255, 255, 255};
|
||||
bind_for_upload(white);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
|
||||
}
|
||||
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
{
|
||||
auto &array{m_arrays[i]};
|
||||
array.handle = make_texture(names[i], GL_TEXTURE_2D_ARRAY);
|
||||
array.size = array_sizes[i];
|
||||
array.srgb = (i == 0);
|
||||
array.format = (array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8);
|
||||
make_storage(array, initial_capacity);
|
||||
}
|
||||
m_capacity = initial_capacity;
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
|
||||
try
|
||||
{
|
||||
m_program = GfxRenderer->Fetch_Shader("terrain");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
texture_handle opengl46_terrain_materials::make_texture(std::string const &Name, GLenum const Target)
|
||||
void opengl46_terrain_materials::gpu_texture(texture_source &Texture, texture_kind const Kind)
|
||||
{
|
||||
auto const handle{m_textures.create(Name, false, GL_RGBA)};
|
||||
auto &texture{m_textures.texture(handle)};
|
||||
auto &texture{static_cast<opengl_texture &>(Texture)};
|
||||
if (texture.id == opengl_texture::invalid_id)
|
||||
::glGenTextures(1, &texture.id);
|
||||
texture.target = Target;
|
||||
// the content is supplied here, the texture manager isn't to load, release or replace it
|
||||
texture.is_ready = true;
|
||||
texture.is_static = true;
|
||||
texture.is_rendertarget = true;
|
||||
texture.is_texstub = false;
|
||||
texture.data_state = resource_state::good;
|
||||
return handle;
|
||||
texture.target = (Kind == texture_kind::layers ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::make_storage(texture_array &Array, int const Layers)
|
||||
void opengl46_terrain_materials::gpu_white(texture_source &Texture)
|
||||
{
|
||||
auto &texture{m_textures.texture(Array.handle)};
|
||||
auto const &white{static_cast<opengl_texture &>(Texture)};
|
||||
std::uint8_t const texel[4]{255, 255, 255, 255};
|
||||
bind_for_upload(white);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh)
|
||||
{
|
||||
auto &texture{m_gltextures.texture(Array.handle)};
|
||||
if (Fresh)
|
||||
{
|
||||
::glDeleteTextures(1, &texture.id);
|
||||
::glGenTextures(1, &texture.id);
|
||||
}
|
||||
auto const format{Array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8};
|
||||
bind_for_upload(texture);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
set_anisotropy(GL_TEXTURE_2D_ARRAY);
|
||||
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, Array.format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
texture.data_width = Array.size;
|
||||
texture.data_height = Array.size;
|
||||
texture.layers = Layers;
|
||||
texture.data_format = Array.format;
|
||||
texture.data_format = format;
|
||||
texture.data_components = GL_RGBA;
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::read_material(layer &Layer)
|
||||
void opengl46_terrain_materials::gpu_blank(slot const &Slot)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
Layer.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
|
||||
Layer.normalmap = (material.shader && material.shader->texture_conf.count("normalmap") > 0);
|
||||
Layer.specgloss = (material.shader && material.shader->texture_conf.count("specgloss") > 0);
|
||||
Layer.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
|
||||
}
|
||||
|
||||
int opengl46_terrain_materials::layer_index(gfx::terrain_layer const &Layer)
|
||||
{
|
||||
for (auto const &existing : m_layers)
|
||||
if (existing.material == Layer.material && existing.size == Layer.size)
|
||||
return existing.index;
|
||||
|
||||
layer entry;
|
||||
entry.material = Layer.material;
|
||||
entry.size = Layer.size;
|
||||
entry.index = static_cast<int>(m_layers.size());
|
||||
read_material(entry);
|
||||
m_layers.push_back(entry);
|
||||
m_pending = true;
|
||||
|
||||
if (static_cast<int>(m_layers.size()) > m_capacity)
|
||||
{
|
||||
// more room: the arrays are made anew, and every layer copied again
|
||||
auto const capacity{m_capacity * 2};
|
||||
for (auto &array : m_arrays)
|
||||
{
|
||||
auto &texture{m_textures.texture(array.handle)};
|
||||
::glDeleteTextures(1, &texture.id);
|
||||
::glGenTextures(1, &texture.id);
|
||||
make_storage(array, capacity);
|
||||
}
|
||||
m_capacity = capacity;
|
||||
for (auto &existing : m_layers)
|
||||
{
|
||||
existing.copied = false;
|
||||
existing.attempts = 0;
|
||||
}
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
return entry.index;
|
||||
}
|
||||
|
||||
std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse)
|
||||
{
|
||||
if (Reuse != null_handle)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Reuse)};
|
||||
if (lookup != m_slotof.end())
|
||||
{
|
||||
m_slots[lookup->second].used = true;
|
||||
return lookup->second;
|
||||
}
|
||||
}
|
||||
if (false == m_free.empty())
|
||||
{
|
||||
auto const index{m_free.back()};
|
||||
m_free.pop_back();
|
||||
m_slots[index].used = true;
|
||||
return index;
|
||||
}
|
||||
auto const index{m_slots.size()};
|
||||
auto const suffix{std::to_string(index)};
|
||||
slot entry;
|
||||
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, GL_TEXTURE_2D);
|
||||
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, GL_TEXTURE_2D);
|
||||
entry.table = make_texture("internal_src:terrain/table_" + suffix, GL_TEXTURE_2D);
|
||||
entry.holes = make_texture("internal_src:terrain/holes_" + suffix, GL_TEXTURE_2D);
|
||||
// the textures get their content before the material is made of them, which checks they're there
|
||||
std::uint8_t const blank[4]{0, 0, 0, 0};
|
||||
for (auto const handle : entry.weights)
|
||||
for (auto const handle : Slot.weights)
|
||||
{
|
||||
bind_for_upload(m_textures.texture(handle));
|
||||
bind_for_upload(m_gltextures.texture(handle));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
@@ -206,7 +101,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
{
|
||||
bind_for_upload(m_textures.texture(entry.table));
|
||||
bind_for_upload(m_gltextures.texture(Slot.table));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
std::array<float, max_layers * 2 * 4> const zeros{};
|
||||
@@ -214,7 +109,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
}
|
||||
{
|
||||
// led between the samples, so the edges of the holes run smoothly across the cells
|
||||
bind_for_upload(m_textures.texture(entry.holes));
|
||||
bind_for_upload(m_gltextures.texture(Slot.holes));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
@@ -224,281 +119,89 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
opengl_texture::reset_unit_cache();
|
||||
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
|
||||
std::string const definition{
|
||||
"shader: terrain\n"
|
||||
"texture1: " + name(m_white) + "\n"
|
||||
"texture2: " + name(entry.weights[0]) + "\n"
|
||||
"texture3: " + name(entry.weights[1]) + "\n"
|
||||
"texture4: " + name(m_arrays[0].handle) + "\n"
|
||||
"texture5: " + name(m_arrays[1].handle) + "\n"
|
||||
"texture6: " + name(m_arrays[2].handle) + "\n"
|
||||
"texture7: " + name(entry.table) + "\n"
|
||||
"texture8: " + name(entry.holes) + "\n"};
|
||||
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
|
||||
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't make the material of a chunk");
|
||||
m_state = -1;
|
||||
return static_cast<std::size_t>(-1);
|
||||
}
|
||||
entry.used = true;
|
||||
m_slots.push_back(entry);
|
||||
m_slotof.emplace(entry.material, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
material_handle opengl46_terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples, std::uint8_t const *Holes)
|
||||
void opengl46_terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels)
|
||||
{
|
||||
if (Layers.empty() || false == init())
|
||||
return null_handle;
|
||||
auto const count{std::min(Layers.size(), max_layers)};
|
||||
std::array<int, max_layers> indices{};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (Layers[i].material == null_handle)
|
||||
return null_handle;
|
||||
indices[i] = layer_index(Layers[i]);
|
||||
}
|
||||
auto const slotindex{acquire_slot(Reuse)};
|
||||
if (slotindex == static_cast<std::size_t>(-1))
|
||||
return null_handle;
|
||||
auto &entry{m_slots[slotindex]};
|
||||
|
||||
// weights, four layers to a texture
|
||||
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
|
||||
auto const side{painted ? Samples + 1 : 1};
|
||||
auto const samples{static_cast<std::size_t>(side) * side};
|
||||
std::vector<std::uint8_t> texels(samples * 4);
|
||||
for (std::size_t texture = 0; texture < 2; ++texture)
|
||||
{
|
||||
std::fill(texels.begin(), texels.end(), 0);
|
||||
for (std::size_t channel = 0; channel < 4; ++channel)
|
||||
{
|
||||
auto const layer{texture * 4 + channel};
|
||||
if (layer >= count)
|
||||
break;
|
||||
if (false == painted)
|
||||
{
|
||||
if (layer == 0)
|
||||
texels[channel] = 255;
|
||||
continue;
|
||||
}
|
||||
auto const *source{Weights + layer * samples};
|
||||
for (std::size_t i = 0; i < samples; ++i)
|
||||
texels[i * 4 + channel] = source[i];
|
||||
}
|
||||
bind_for_upload(m_textures.texture(entry.weights[texture]));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
entry.count = count;
|
||||
entry.layers = indices;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
entry.rotations[i] = Layers[i].rotation;
|
||||
write_table(entry);
|
||||
|
||||
// the holes: the cells cut out entirely are left out of the mesh, the program of the holes cuts the rest of the openings
|
||||
auto const cut{Holes != nullptr && HoleSamples > 0};
|
||||
{
|
||||
auto const holeside{cut ? HoleSamples + 1 : 1};
|
||||
std::uint8_t const none{0};
|
||||
bind_for_upload(m_textures.texture(entry.holes));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, holeside, holeside, 0, GL_RED, GL_UNSIGNED_BYTE, cut ? Holes : &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
auto &material{m_materials.material(entry.material)};
|
||||
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
|
||||
if (m_holesprogram == nullptr && cut)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_holesprogram = GfxRenderer->Fetch_Shader("terrain_holes");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the shader of the holes: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
// the program of the holes discards fragments, which the depth prepass of the plain one never does
|
||||
material.shader = (cut && m_holesprogram != nullptr) ? m_holesprogram : m_program;
|
||||
return entry.material;
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Side, Side, 0, GL_RGBA, GL_UNSIGNED_BYTE, Texels);
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::write_table(slot const &Slot)
|
||||
void opengl46_terrain_materials::gpu_table(texture_handle const Texture, float const *Table)
|
||||
{
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, Table);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples)
|
||||
{
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, Side, Side, 0, GL_RED, GL_UNSIGNED_BYTE, Samples);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_changed()
|
||||
{
|
||||
// what the layers are: index in the arrays, repeat, which maps they have, reflection
|
||||
std::array<float, max_layers * 2 * 4> table{};
|
||||
for (std::size_t i = 0; i < Slot.count; ++i)
|
||||
{
|
||||
auto const &layer{m_layers[Slot.layers[i]]};
|
||||
table[i * 4 + 0] = static_cast<float>(layer.index);
|
||||
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
|
||||
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
|
||||
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
|
||||
table[(max_layers + i) * 4 + 0] = layer.reflection;
|
||||
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
|
||||
auto const angle{glm::radians(static_cast<double>(Slot.rotations[i]))};
|
||||
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
|
||||
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
|
||||
}
|
||||
bind_for_upload(m_textures.texture(Slot.table));
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, table.data());
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::on_environment_change()
|
||||
void opengl46_terrain_materials::gpu_begin_copies()
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return;
|
||||
bool changed{false};
|
||||
for (auto &layer : m_layers)
|
||||
{
|
||||
auto const &material{m_materials.material(layer.material)};
|
||||
if (false == (material.update_on_season_change || material.update_on_weather_change))
|
||||
continue;
|
||||
// the reloaded material can have other textures, other maps and another size
|
||||
read_material(layer);
|
||||
layer.copied = false;
|
||||
layer.attempts = 0;
|
||||
changed = true;
|
||||
}
|
||||
if (false == changed)
|
||||
return;
|
||||
m_pending = true;
|
||||
// the tables of the chunks hold what the layers have, freed slots get theirs anew when they're used again
|
||||
for (auto const &entry : m_slots)
|
||||
if (entry.used)
|
||||
write_table(entry);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::release(material_handle const Material)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Material)};
|
||||
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
|
||||
return;
|
||||
m_slots[lookup->second].used = false;
|
||||
m_free.push_back(lookup->second);
|
||||
}
|
||||
|
||||
bool opengl46_terrain_materials::copy_layer(layer &Layer)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
auto const slot = [&](char const *Name, int const Fallback) {
|
||||
if (material.shader)
|
||||
{
|
||||
auto const lookup{material.shader->texture_conf.find(Name)};
|
||||
if (lookup != material.shader->texture_conf.end())
|
||||
return static_cast<int>(lookup->second.id);
|
||||
return -1;
|
||||
}
|
||||
return Fallback;
|
||||
};
|
||||
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
|
||||
// all maps of the layer have to be ready, so they go together
|
||||
std::array<opengl_texture *, 3> sources{};
|
||||
for (std::size_t i = 0; i < slots.size(); ++i)
|
||||
{
|
||||
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
|
||||
continue;
|
||||
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
|
||||
if (false == texture.is_ready)
|
||||
{
|
||||
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
|
||||
// one released by the sweep or never read has to be read first
|
||||
if (texture.data_state == resource_state::none)
|
||||
{
|
||||
texture.reload_on_use = false;
|
||||
texture.load();
|
||||
}
|
||||
if (texture.data_state == resource_state::failed)
|
||||
continue;
|
||||
if (false == texture.create())
|
||||
{
|
||||
if (++Layer.attempts < copy_attempts)
|
||||
return false;
|
||||
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
|
||||
continue; // waited long enough, the layer goes without it
|
||||
}
|
||||
}
|
||||
sources[i] = &texture;
|
||||
}
|
||||
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
|
||||
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
{
|
||||
auto const &array{m_arrays[i]};
|
||||
auto &target{m_textures.texture(array.handle)};
|
||||
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer.index);
|
||||
m_framebuffer->setup_drawing(1);
|
||||
::glViewport(0, 0, array.size, array.size);
|
||||
if (sources[i] == nullptr)
|
||||
{
|
||||
::glClearBufferfv(GL_COLOR, 0, defaults[i].data());
|
||||
continue;
|
||||
}
|
||||
if (array.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
m_copy->apply(*sources[i], m_framebuffer.get());
|
||||
}
|
||||
Layer.copied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::update()
|
||||
{
|
||||
if (false == m_pending || m_state <= 0)
|
||||
return;
|
||||
|
||||
auto const blend{::glIsEnabled(GL_BLEND)};
|
||||
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST)};
|
||||
auto const cull{::glIsEnabled(GL_CULL_FACE)};
|
||||
auto const depth{::glIsEnabled(GL_DEPTH_TEST)};
|
||||
auto const srgb{::glIsEnabled(GL_FRAMEBUFFER_SRGB)};
|
||||
m_saved.blend = ::glIsEnabled(GL_BLEND);
|
||||
m_saved.scissor = ::glIsEnabled(GL_SCISSOR_TEST);
|
||||
m_saved.cull = ::glIsEnabled(GL_CULL_FACE);
|
||||
m_saved.depth = ::glIsEnabled(GL_DEPTH_TEST);
|
||||
m_saved.srgb = ::glIsEnabled(GL_FRAMEBUFFER_SRGB);
|
||||
::glDisable(GL_BLEND);
|
||||
::glDisable(GL_SCISSOR_TEST);
|
||||
::glDisable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
bool copied{false};
|
||||
m_pending = false;
|
||||
for (auto &layer : m_layers)
|
||||
void opengl46_terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default)
|
||||
{
|
||||
auto &target{m_gltextures.texture(Array.handle)};
|
||||
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer);
|
||||
m_framebuffer->setup_drawing(1);
|
||||
::glViewport(0, 0, Array.size, Array.size);
|
||||
if (Source == nullptr)
|
||||
{
|
||||
if (layer.copied)
|
||||
continue;
|
||||
if (copy_layer(layer))
|
||||
copied = true;
|
||||
else
|
||||
m_pending = true;
|
||||
::glClearBufferfv(GL_COLOR, 0, Default.data());
|
||||
return;
|
||||
}
|
||||
if (copied)
|
||||
if (Array.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
m_copy->apply(static_cast<opengl_texture &>(*Source), m_framebuffer.get());
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_end_copies(bool const Copied)
|
||||
{
|
||||
if (Copied)
|
||||
{
|
||||
for (auto const &array : m_arrays)
|
||||
for (auto const &array : arrays())
|
||||
{
|
||||
bind_for_upload(m_textures.texture(array.handle));
|
||||
bind_for_upload(m_gltextures.texture(array.handle));
|
||||
::glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
// leave the state as the rest of the renderer knows it
|
||||
gl::framebuffer::unbind();
|
||||
if (blend)
|
||||
if (m_saved.blend)
|
||||
::glEnable(GL_BLEND);
|
||||
if (scissor)
|
||||
if (m_saved.scissor)
|
||||
::glEnable(GL_SCISSOR_TEST);
|
||||
if (cull)
|
||||
if (m_saved.cull)
|
||||
::glEnable(GL_CULL_FACE);
|
||||
if (depth)
|
||||
if (m_saved.depth)
|
||||
::glEnable(GL_DEPTH_TEST);
|
||||
else
|
||||
::glDisable(GL_DEPTH_TEST);
|
||||
if (srgb)
|
||||
if (m_saved.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
|
||||
@@ -9,91 +9,44 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "model/material.h"
|
||||
#include "model/Texture.h"
|
||||
#include "rendering/common/terrainmaterials.h"
|
||||
#include "gl/framebuffer.h"
|
||||
#include "gl/postfx.h"
|
||||
|
||||
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes).
|
||||
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
|
||||
// of its own, with textures holding the weights of its layers and a table of what its layers are
|
||||
class opengl46_terrain_materials
|
||||
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes):
|
||||
// the textures of gfx::terrain_materials as opengl textures, the layer maps copied into the arrays by a draw of the copy shader
|
||||
class opengl46_terrain_materials : public gfx::terrain_materials
|
||||
{
|
||||
public:
|
||||
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : m_materials(Materials), m_textures(Textures) {}
|
||||
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : gfx::terrain_materials(Materials, Textures), m_gltextures(Textures) {}
|
||||
|
||||
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
|
||||
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr);
|
||||
void release(material_handle const Material);
|
||||
// copies the textures of new layers into the arrays; to be called before the frame is drawn
|
||||
void update();
|
||||
// the layer materials which change with the season or the weather were reloaded, their textures are copied again
|
||||
void on_environment_change();
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_texture(texture_source &Texture, texture_kind const Kind) override;
|
||||
void gpu_white(texture_source &Texture) override;
|
||||
void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) override;
|
||||
void gpu_blank(slot const &Slot) override;
|
||||
void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) override;
|
||||
void gpu_table(texture_handle const Texture, float const *Table) override;
|
||||
void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) override;
|
||||
void gpu_begin_copies() override;
|
||||
void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) override;
|
||||
void gpu_end_copies(bool const Copied) override;
|
||||
void gpu_changed() override;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t max_layers{8};
|
||||
|
||||
struct layer
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
float size{0.f}; // as given by the chunk
|
||||
float repeat{8.f}; // metres the textures are repeated at
|
||||
int index{0}; // in the arrays
|
||||
bool normalmap{false};
|
||||
bool specgloss{false};
|
||||
float reflection{0.f};
|
||||
bool copied{false};
|
||||
int attempts{0};
|
||||
};
|
||||
struct texture_array
|
||||
{
|
||||
texture_handle handle{null_handle};
|
||||
int size{0};
|
||||
GLint format{0};
|
||||
bool srgb{false};
|
||||
};
|
||||
struct slot
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
std::array<texture_handle, 2> weights{null_handle, null_handle};
|
||||
texture_handle table{null_handle};
|
||||
texture_handle holes{null_handle}; // where the ground is cut, R8; a single texel of 0 without holes
|
||||
bool used{false};
|
||||
// what the table was made of, kept to make it again when a layer changes
|
||||
std::size_t count{0};
|
||||
std::array<int, max_layers> layers{};
|
||||
std::array<float, max_layers> rotations{};
|
||||
};
|
||||
|
||||
bool init();
|
||||
texture_handle make_texture(std::string const &Name, GLenum const Target);
|
||||
void make_storage(texture_array &Array, int const Layers);
|
||||
void read_material(layer &Layer);
|
||||
int layer_index(gfx::terrain_layer const &Layer);
|
||||
void write_table(slot const &Slot);
|
||||
bool copy_layer(layer &Layer);
|
||||
std::size_t acquire_slot(material_handle const Reuse);
|
||||
|
||||
material_manager &m_materials;
|
||||
texture_manager &m_textures;
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
texture_handle m_white{null_handle};
|
||||
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
|
||||
int m_capacity{0}; // layers the arrays have room for
|
||||
std::vector<layer> m_layers;
|
||||
bool m_pending{false};
|
||||
std::vector<slot> m_slots;
|
||||
std::vector<std::size_t> m_free;
|
||||
std::unordered_map<material_handle, std::size_t> m_slotof;
|
||||
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
|
||||
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
|
||||
texture_manager &m_gltextures;
|
||||
std::unique_ptr<gl::postfx> m_copy;
|
||||
std::unique_ptr<gl::framebuffer> m_framebuffer;
|
||||
// the state of opengl the copies change, given back after them
|
||||
struct
|
||||
{
|
||||
GLboolean blend{GL_FALSE};
|
||||
GLboolean scissor{GL_FALSE};
|
||||
GLboolean cull{GL_FALSE};
|
||||
GLboolean depth{GL_FALSE};
|
||||
GLboolean srgb{GL_FALSE};
|
||||
} m_saved;
|
||||
};
|
||||
|
||||
@@ -11,14 +11,11 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46weather.h"
|
||||
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
#include "environment/starcatalog.h"
|
||||
#include "environment/weather.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "widgets/weatherpanel.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -27,47 +24,6 @@ namespace
|
||||
int const SHAPE_NOISE_SIZE{128};
|
||||
int const DETAIL_NOISE_SIZE{32};
|
||||
int const COVERAGE_NOISE_SIZE{512};
|
||||
// how many times the cell of a genus fits in the tile of the shape noise, and its spread in the tile of the coverage noise
|
||||
double const SHAPE_TILE_CELLS{4.0};
|
||||
double const COVERAGE_TILE_SPREADS{4.0};
|
||||
// size of the patches of the ground fog
|
||||
float const FOG_PATCH_SIZE{300.f};
|
||||
// the haze the atmosphere draws in the sky already, as an extinction: about the haze of a day with 30 km of visibility
|
||||
float const SKY_HAZE_EXTINCTION{3.f / 30000.f};
|
||||
// near end of the fog volume (view depth, metres)
|
||||
float const FOG_VOLUME_NEAR{0.5f};
|
||||
// bands the panorama is drawn in, one a frame
|
||||
int const PANORAMA_BANDS{8};
|
||||
// brightness of a star of magnitude 0 in the units of the scene
|
||||
float const STAR_BRIGHTNESS{4.f};
|
||||
// stars dimmer than this aren't drawn
|
||||
float const STAR_LIMIT{6.5f};
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
// the position in a tiling noise, of a period in metres, kept small for the single precision of the shaders
|
||||
double wrap(double const Value, double const Period)
|
||||
{
|
||||
return Value - Period * std::floor(Value / Period);
|
||||
}
|
||||
|
||||
// the colour of a star of a colour temperature, linear, of luminance 1, somewhat paler than the black body: the eye sees most
|
||||
// stars white
|
||||
glm::vec3 star_color(float const Temperature)
|
||||
{
|
||||
auto const t{std::clamp(Temperature, 1500.f, 40000.f) / 100.f};
|
||||
glm::vec3 color;
|
||||
color.r = (t <= 66.f) ? 255.f : 329.698727446f * std::pow(t - 60.f, -0.1332047592f);
|
||||
color.g = (t <= 66.f) ? 99.4708025861f * std::log(t) - 161.1195681661f : 288.1221695283f * std::pow(t - 60.f, -0.0755148492f);
|
||||
color.b = (t >= 66.f) ? 255.f : (t <= 19.f) ? 0.f : 138.5177312231f * std::log(t - 10.f) - 305.0447927307f;
|
||||
color = glm::pow(glm::clamp(color / 255.f, glm::vec3{0.f}, glm::vec3{1.f}), glm::vec3{2.2f});
|
||||
color /= std::max(luminance(color), 1e-3f);
|
||||
return glm::mix(color, glm::vec3{1.f}, 0.5f);
|
||||
}
|
||||
|
||||
std::unique_ptr<gl::program> make_compute(std::string const &Name)
|
||||
{
|
||||
gl::shader shader(Name);
|
||||
@@ -118,26 +74,6 @@ GLuint opengl46_weather::create_texture(GLenum const Target, GLenum const Format
|
||||
return texture;
|
||||
}
|
||||
|
||||
opengl46_weather::quality_level opengl46_weather::quality(int const Clouds, int const Fog)
|
||||
{
|
||||
quality_level level;
|
||||
switch (std::clamp(Clouds, 1, 4))
|
||||
{
|
||||
case 1: level = {4, 32, 3, false, {512, 128}, {}}; break;
|
||||
case 2: level = {2, 48, 4, true, {1024, 256}, {}}; break;
|
||||
case 3: level = {2, 80, 6, true, {1024, 256}, {}}; break;
|
||||
default: level = {2, 128, 8, true, {2048, 512}, {}}; break;
|
||||
}
|
||||
switch (std::clamp(Fog, 1, 4))
|
||||
{
|
||||
case 1: level.fog_grid = {80, 45, 48}; break;
|
||||
case 2: level.fog_grid = {160, 90, 64}; break;
|
||||
case 3: level.fog_grid = {160, 90, 96}; break;
|
||||
default: level.fog_grid = {240, 135, 128}; break;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
void opengl46_weather::init()
|
||||
{
|
||||
m_noise_program = make_compute("clouds_noise.comp");
|
||||
@@ -150,32 +86,12 @@ void opengl46_weather::init()
|
||||
m_sky_program = make_program("quad.vert", "sky_weather.frag");
|
||||
m_stars_program = make_program("stars.vert", "stars.frag");
|
||||
|
||||
// a grey fog and no clouds until the first update
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{0.5f, 0.5f, 0.5f, 1.f};
|
||||
m_weather.fog_haze = glm::vec4{3.f / 10000.f, 1.f / 800.f, SKY_HAZE_EXTINCTION, 1.f};
|
||||
m_weather.fog_light = glm::vec4{0.f, 1.f, 0.6f, 1.f / 5000.f};
|
||||
m_weather_ubo = std::make_unique<gl::ubo>(sizeof(gl::weather_ubs), gl::WEATHER_UBO_BINDING);
|
||||
m_weather_ubo->update(m_weather);
|
||||
m_weather_ubo->update(m_state.block());
|
||||
|
||||
make_noise();
|
||||
|
||||
// the stars, in the equatorial frame
|
||||
std::vector<glm::vec4> stars;
|
||||
auto const *catalogue{star_catalogue::stars()};
|
||||
for (std::size_t i = 0; i < star_catalogue::size(); ++i)
|
||||
{
|
||||
auto const &star{catalogue[i]};
|
||||
auto const magnitude{static_cast<float>(star.magnitude) * 0.01f};
|
||||
if (magnitude > STAR_LIMIT)
|
||||
continue;
|
||||
auto const ascension{glm::radians(static_cast<float>(star.right_ascension) * 0.01f)};
|
||||
auto const declination{glm::radians(static_cast<float>(star.declination) * 0.01f)};
|
||||
stars.emplace_back(std::cos(declination) * std::cos(ascension), std::cos(declination) * std::sin(ascension), std::sin(declination), magnitude);
|
||||
// a phase of the twinkling which doesn't repeat from star to star
|
||||
auto const phase{std::fmod(static_cast<float>(i) * 0.6180339887f, 1.f)};
|
||||
stars.emplace_back(star_color(static_cast<float>(star.temperature) * 10.f), phase);
|
||||
}
|
||||
auto const stars{gfx::weather_state::star_vertices()};
|
||||
m_stars_count = static_cast<int>(stars.size() / 2);
|
||||
m_stars_vao.emplace();
|
||||
m_stars_vao->bind();
|
||||
@@ -249,105 +165,15 @@ void opengl46_weather::bind_noise() const
|
||||
void opengl46_weather::upload_header()
|
||||
{
|
||||
// the colours of the horizon are left alone when the gpu writes them
|
||||
auto const size{m_horizon_on_gpu ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
|
||||
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_weather), 0, static_cast<GLsizeiptr>(size));
|
||||
auto const size{m_state.horizon_on_gpu() ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
|
||||
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_state.block()), 0, static_cast<GLsizeiptr>(size));
|
||||
m_weather_ubo->bind_uniform();
|
||||
}
|
||||
|
||||
void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera)
|
||||
{
|
||||
auto const &weather{simulation::Environment.weather()};
|
||||
auto const &conditions{weather.conditions()};
|
||||
m_fog_volume_valid = false;
|
||||
m_clouds_view_valid = false;
|
||||
++m_frame;
|
||||
|
||||
auto const height{static_cast<float>(Camera.y) - weather.ground()};
|
||||
auto const time{weather.time()};
|
||||
auto const sun{glm::normalize(Keylight)};
|
||||
|
||||
// the fog. the visibility is where a dark object fades to 5%: the extinction is 3 over it
|
||||
auto const haze{3.f / conditions.visibility};
|
||||
auto const hazerate{1.f / conditions.haze_height};
|
||||
m_weather.fog_haze = glm::vec4{haze, hazerate, SKY_HAZE_EXTINCTION, 1.f};
|
||||
if (conditions.groundfog_thickness > 0.f)
|
||||
{
|
||||
auto const thickness{conditions.groundfog_thickness};
|
||||
m_weather.fog_ground = glm::vec4{std::max(3.f / conditions.groundfog_visibility - haze, 0.f), 1.f / (0.25f * thickness), 0.75f * thickness, conditions.fog_noise};
|
||||
}
|
||||
else
|
||||
m_weather.fog_ground = glm::vec4{0.f, 1.f, 0.f, conditions.fog_noise};
|
||||
// the patches drift with the wind, slower than the clouds
|
||||
auto const drift{glm::dvec2{weather.cloud_offset()} * 0.3};
|
||||
auto const patch{static_cast<double>(FOG_PATCH_SIZE)};
|
||||
m_weather.fog_noise = glm::vec4{static_cast<float>(wrap(Camera.x - drift.x, patch)), static_cast<float>(wrap(Camera.y, patch)), static_cast<float>(wrap(Camera.z - drift.y, patch)), 1.f / FOG_PATCH_SIZE};
|
||||
// the extinction at the height of the camera tells how far things are hidden
|
||||
auto const extinction{haze * std::exp(-hazerate * std::max(height, 0.f)) +
|
||||
m_weather.fog_ground.x * std::exp(-m_weather.fog_ground.y * std::max(height - m_weather.fog_ground.z, 0.f))};
|
||||
// 2% left at 1.3 visibilities; the draw range is twice this
|
||||
m_fog_range = std::clamp(3.f / std::max(extinction, 1e-7f) / 1.5f, 5.f, 1e6f);
|
||||
|
||||
// the share of the light of the fog the sun gives, which the shadows hold back
|
||||
auto const sunlight{luminance(Keycolor) * 1.5f * std::max(sun.y, 0.f)};
|
||||
auto const skylight{luminance(Ambient) * 0.65f};
|
||||
m_sun_share = (sunlight + skylight > 1e-6f) ? sunlight / (sunlight + skylight) : 0.f;
|
||||
|
||||
// clouds
|
||||
auto const layers{std::min(conditions.clouds.size(), gl::MAX_CLOUD_LAYERS)};
|
||||
m_clouds_drawn = (Global.gfx_clouds > 0) && (layers > 0);
|
||||
m_average_transmittance = 1.f;
|
||||
auto const velocity{weather.cloud_velocity()};
|
||||
auto const speed{glm::length(velocity)};
|
||||
auto const wind{speed > 1e-3f ? glm::dvec2{velocity / speed} : glm::dvec2{1.0, 0.0}};
|
||||
m_weather.cloud_wind = glm::vec4{static_cast<float>(wind.x), static_cast<float>(wind.y), speed, m_cloud_detail_scale};
|
||||
for (std::size_t i = 0; i < gl::MAX_CLOUD_LAYERS; ++i)
|
||||
{
|
||||
auto &target{m_weather.cloud_layers[i]};
|
||||
if (i >= layers)
|
||||
{
|
||||
target = gl::cloud_layer_ubs{};
|
||||
continue;
|
||||
}
|
||||
auto const &layer{conditions.clouds[i]};
|
||||
auto const &genus{cloud_genus_info(layer.type)};
|
||||
target.heights = glm::vec4{layer.base, layer.top, 1.f / std::max(layer.top - layer.base, 1.f), static_cast<float>(layer.type)};
|
||||
auto const shapetile{static_cast<double>(genus.cell) * SHAPE_TILE_CELLS};
|
||||
auto const coveragetile{static_cast<double>(genus.spread) * COVERAGE_TILE_SPREADS};
|
||||
target.shape = glm::vec4{layer.coverage, genus.extinction * layer.density, static_cast<float>(1.0 / shapetile), genus.erosion};
|
||||
target.profile = glm::vec4{genus.bottom, genus.top, genus.top_variation, genus.anvil};
|
||||
// the higher, the faster the wind carries them
|
||||
auto const offset{glm::dvec2{weather.cloud_offset()} * (1.0 + static_cast<double>(layer.base) / 8000.0)};
|
||||
// the camera's place in the noise, in the space stretched along the wind as the shaders take it
|
||||
auto const place{glm::dvec2{Camera.x, Camera.z} - offset};
|
||||
auto const along{glm::dot(place, wind)};
|
||||
auto const stretched{(place - wind * along) + wind * (along / std::max(static_cast<double>(genus.stretch), 1.0))};
|
||||
target.motion = glm::vec4{static_cast<float>(wrap(stretched.x, shapetile)), static_cast<float>(wrap(stretched.y, shapetile)), static_cast<float>(1.0 / coveragetile), genus.stretch};
|
||||
// the clouds change their shapes slowly; the seed moves the layers apart in the noise
|
||||
auto const evolution{std::fmod(static_cast<double>(conditions.seed) * 0.6180339887 + static_cast<double>(i) * 0.37 + time * 0.00002, 1.0)};
|
||||
target.extra = glm::vec4{genus.flat ? 1.f : 0.f, static_cast<float>(evolution), static_cast<float>(wrap(stretched.x, coveragetile)), static_cast<float>(wrap(stretched.y, coveragetile))};
|
||||
m_average_transmittance *= 1.f - std::clamp(layer.coverage * genus.opacity * std::min(layer.density, 1.5f), 0.f, 1.f);
|
||||
}
|
||||
if (Global.gfx_clouds <= 0)
|
||||
m_average_transmittance = 1.f;
|
||||
m_weather.fog_light = glm::vec4{m_sun_share, m_average_transmittance, 0.6f, fog_density()};
|
||||
m_weather.weather_camera = glm::vec4{height, static_cast<float>(std::fmod(time, 100000.0)), m_clouds_drawn ? 1.f : 0.f, m_clouds_drawn ? static_cast<float>(layers) : 0.f};
|
||||
m_weather.weather_sun_direction = glm::vec4{sun, sun.y > -0.02f ? 1.f : 0.f};
|
||||
m_weather.weather_sun_color = glm::vec4{Keycolor, m_cloud_sun_scale};
|
||||
m_weather.weather_ambient = glm::vec4{Ambient * 1.3f, m_cloud_sky_scale};
|
||||
m_weather.fog_volume_params = glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target = glm::vec4{1.f, 1.f, Global.gfx_fog_range, 1.f - Sky.atmosphere_weight()};
|
||||
|
||||
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
|
||||
m_horizon_on_gpu = (Sky.atmosphere_weight() > 0.f) && (Sky.skyview_table() != 0);
|
||||
if (false == m_horizon_on_gpu)
|
||||
{
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{Fogcolor, 1.f};
|
||||
}
|
||||
|
||||
// the shadows of the clouds, on a map snapped to its texels so it doesn't crawl as the camera moves
|
||||
auto const shadows{m_clouds_drawn && Global.gfx_clouds_shadows && (sun.y > 0.02f)};
|
||||
m_weather.cloud_shadow_params = glm::vec4{0.f};
|
||||
m_state.update(Sky.atmosphere_weight(), Sky.skyview_table() != 0, Keylight, Keycolor, Ambient, Fogcolor, Camera);
|
||||
auto const shadows{m_state.shadows_wanted()};
|
||||
if (shadows)
|
||||
{
|
||||
if (m_shadow_size != Global.gfx_clouds_shadows_size)
|
||||
@@ -356,14 +182,11 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
m_shadow_size = Global.gfx_clouds_shadows_size;
|
||||
m_shadow_map = create_texture(GL_TEXTURE_2D, GL_R16F, glm::ivec3{m_shadow_size, m_shadow_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
auto const extent{static_cast<double>(Global.gfx_clouds_shadows_range)};
|
||||
auto const texel{extent / static_cast<double>(m_shadow_size)};
|
||||
auto const centre{glm::dvec2{std::round(Camera.x / texel) * texel - Camera.x, std::round(Camera.z / texel) * texel - Camera.z}};
|
||||
m_weather.cloud_shadow_params = glm::vec4{static_cast<float>(centre.x), static_cast<float>(centre.y), static_cast<float>(1.0 / extent), 0.f};
|
||||
m_state.place_shadows(m_shadow_size, Camera);
|
||||
}
|
||||
upload_header();
|
||||
|
||||
if (m_horizon_on_gpu)
|
||||
if (m_state.horizon_on_gpu())
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, Sky.skyview_table());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, gl::WEATHER_SCRATCH_SSBO_BINDING, *m_weather_ubo);
|
||||
@@ -383,35 +206,32 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
bind_noise();
|
||||
glBindImageTexture(0, m_shadow_map, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
|
||||
glProgramUniform1i(*m_shadow_program, 1, m_shadow_size);
|
||||
glProgramUniform3f(*m_shadow_program, 2, m_weather.cloud_shadow_params.x, m_weather.cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
|
||||
glProgramUniform3f(*m_shadow_program, 2, m_state.block().cloud_shadow_params.x, m_state.block().cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
|
||||
glProgramUniform1i(*m_shadow_program, 3, 8);
|
||||
m_shadow_program->dispatch(groups(m_shadow_size), groups(m_shadow_size), 1);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
|
||||
m_shadow_timer.end();
|
||||
m_shadows_valid = true;
|
||||
m_weather.cloud_shadow_params.w = 1.f;
|
||||
m_state.shadows_drawn(true);
|
||||
upload_header();
|
||||
}
|
||||
else
|
||||
m_shadows_valid = false;
|
||||
m_state.shadows_drawn(false);
|
||||
|
||||
// a band of the panorama of the clouds, all of it the first time
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
if (m_panorama_size != level.panorama)
|
||||
{
|
||||
release(m_panorama);
|
||||
m_panorama_size = level.panorama;
|
||||
m_panorama = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_panorama_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(m_panorama, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
m_panorama_full = false;
|
||||
m_panorama_band = 0;
|
||||
m_state.panorama_resized();
|
||||
}
|
||||
auto const bandrows{(m_panorama_size.y + PANORAMA_BANDS - 1) / PANORAMA_BANDS};
|
||||
auto const first{m_panorama_full ? m_panorama_band * bandrows : 0};
|
||||
auto const rows{m_panorama_full ? bandrows : m_panorama_size.y};
|
||||
int first, rows;
|
||||
m_state.panorama_band(m_panorama_size, first, rows);
|
||||
m_panorama_timer.begin();
|
||||
bind_noise();
|
||||
glBindImageTexture(0, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -419,7 +239,7 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
glBindImageTexture(1, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glProgramUniform1i(*m_march_program, 0, 1);
|
||||
glProgramUniform2i(*m_march_program, 1, m_panorama_size.x, m_panorama_size.y);
|
||||
glProgramUniform1i(*m_march_program, 4, m_frame);
|
||||
glProgramUniform1i(*m_march_program, 4, m_state.frame());
|
||||
// the panorama is seen blurred: fewer samples
|
||||
glProgramUniform4f(*m_march_program, 5, static_cast<float>(std::max(level.steps / 2, 24)), static_cast<float>(std::max(level.light_steps - 1, 2)), Global.gfx_clouds_distance, 0.f);
|
||||
glProgramUniform2i(*m_march_program, 6, first, rows);
|
||||
@@ -428,17 +248,14 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glBindImageTexture(1, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_panorama_timer.end();
|
||||
m_panorama_band = m_panorama_full ? (m_panorama_band + 1) % PANORAMA_BANDS : 0;
|
||||
m_panorama_full = true;
|
||||
}
|
||||
gl::program::unbind();
|
||||
m_report = to_metar(conditions);
|
||||
}
|
||||
|
||||
void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const &Level)
|
||||
{
|
||||
auto const cloudsize{glm::max((Size + Level.divisor - 1) / Level.divisor, glm::ivec2{1})};
|
||||
if (m_clouds_drawn && (cloudsize != m_view_size || m_clouds_color == 0))
|
||||
if (m_state.clouds_drawn() && (cloudsize != m_view_size || m_clouds_color == 0))
|
||||
{
|
||||
for (auto *texture : {&m_clouds_color, &m_clouds_depth, &m_clouds_history[0], &m_clouds_history[1]})
|
||||
release(*texture);
|
||||
@@ -447,7 +264,7 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
|
||||
m_clouds_depth = create_texture(GL_TEXTURE_2D, GL_R32F, glm::ivec3{m_view_size, 1}, GL_NEAREST, GL_CLAMP_TO_EDGE);
|
||||
for (auto &history : m_clouds_history)
|
||||
history = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_view_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
m_previous_clouds.valid = false;
|
||||
m_state.previous_clouds.valid = false;
|
||||
}
|
||||
if ((Global.gfx_fog == 1) && (Level.fog_grid != m_fog_grid || m_fog_integrated == 0))
|
||||
{
|
||||
@@ -458,23 +275,23 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
|
||||
scattering = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
// one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp)
|
||||
m_fog_integrated = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid + glm::ivec3{0, 0, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
m_previous_fog.valid = false;
|
||||
m_state.previous_fog.valid = false;
|
||||
}
|
||||
m_view_level = Level;
|
||||
}
|
||||
|
||||
void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp)
|
||||
{
|
||||
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
prepare_view(Size, level);
|
||||
auto const rotation{glm::mat4{glm::mat3{View}}};
|
||||
auto const inverseprojection{glm::inverse(Projection)};
|
||||
auto const viewtoworld{glm::transpose(rotation)};
|
||||
auto const projectionview{Projection * rotation};
|
||||
// a jump of the camera (another vehicle, the free camera) leaves nothing to blend with
|
||||
auto const continuity = [&](view_state const &Previous) { return Previous.valid && glm::length(Previous.camera - Camera) < 50.0; };
|
||||
auto &previousclouds{m_state.previous_clouds};
|
||||
auto &previousfog{m_state.previous_fog};
|
||||
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
m_clouds_timer.begin();
|
||||
bind_noise();
|
||||
@@ -484,7 +301,7 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniform2i(*m_march_program, 1, m_view_size.x, m_view_size.y);
|
||||
glProgramUniformMatrix4fv(*m_march_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_march_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniform1i(*m_march_program, 4, m_frame);
|
||||
glProgramUniform1i(*m_march_program, 4, m_state.frame());
|
||||
glProgramUniform4f(*m_march_program, 5, static_cast<float>(level.steps), static_cast<float>(level.light_steps), Global.gfx_clouds_distance, level.detail ? 1.f : 0.f);
|
||||
glProgramUniform2i(*m_march_program, 6, 0, m_view_size.y);
|
||||
m_march_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
|
||||
@@ -499,10 +316,10 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniform2i(*m_temporal_program, 1, m_view_size.x, m_view_size.y);
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_clouds.projection_view));
|
||||
auto const previous{glm::vec3{m_previous_clouds.camera - Camera}};
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(previousclouds.projection_view));
|
||||
auto const previous{glm::vec3{previousclouds.camera - Camera}};
|
||||
glProgramUniform3f(*m_temporal_program, 5, previous.x, previous.y, previous.z);
|
||||
glProgramUniform1f(*m_temporal_program, 6, continuity(m_previous_clouds) ? 0.15f : 1.f);
|
||||
glProgramUniform1f(*m_temporal_program, 6, gfx::weather_state::continuity(previousclouds, Camera) ? 0.15f : 1.f);
|
||||
m_temporal_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -510,31 +327,18 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glBindTextureUnit(gl::CLOUD_SHAPE_TEX, m_shape_noise);
|
||||
m_clouds_current = next;
|
||||
m_clouds_timer.end();
|
||||
m_previous_clouds = {projectionview, Camera, {}, true};
|
||||
m_clouds_view_valid = true;
|
||||
previousclouds = {projectionview, Camera, {}, true};
|
||||
m_state.clouds_made();
|
||||
}
|
||||
else
|
||||
m_previous_clouds.valid = false;
|
||||
previousclouds.valid = false;
|
||||
|
||||
if (Global.gfx_fog == 1)
|
||||
{
|
||||
m_fog_timer.begin();
|
||||
auto const farthest{std::max(Global.gfx_fog_range, FOG_VOLUME_NEAR * 4.f)};
|
||||
// the cells are sampled at another point in every frame: along the depth in the Halton sequence of 2, across the view in
|
||||
// those of 3 and 5 (fog_inject.comp moves the point of every cell by a value of its own)
|
||||
auto const halton = [](int Index, int const Base) {
|
||||
float value{0.f}, fraction{1.f};
|
||||
for (; Index > 0; Index /= Base)
|
||||
{
|
||||
fraction /= static_cast<float>(Base);
|
||||
value += fraction * static_cast<float>(Index % Base);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
auto const temporal{Global.gfx_fog_temporal && continuity(m_previous_fog)};
|
||||
auto const sample{(m_frame % 64) + 1};
|
||||
glm::vec3 const jitter{temporal ? glm::vec3{halton(sample, 3), halton(sample, 5), halton(sample, 2)} : glm::vec3{0.5f}};
|
||||
glm::vec4 const slicing{FOG_VOLUME_NEAR, farthest, static_cast<float>(m_fog_grid.z) / std::log((farthest + gl::FOG_VOLUME_BIAS) / (FOG_VOLUME_NEAR + gl::FOG_VOLUME_BIAS)), jitter.z};
|
||||
auto const temporal{Global.gfx_fog_temporal && gfx::weather_state::continuity(previousfog, Camera)};
|
||||
auto const jitter{m_state.fog_jitter(temporal)};
|
||||
auto const slicing{gfx::weather_state::fog_slicing(m_fog_grid.z, jitter)};
|
||||
bind_noise();
|
||||
auto const next{1 - m_fog_current};
|
||||
glBindImageTexture(0, m_fog_scattering[next], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -543,14 +347,14 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 1, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 2, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniform4fv(*m_inject_program, 3, 1, glm::value_ptr(slicing));
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_fog.projection_view));
|
||||
auto const previous{glm::vec3{m_previous_fog.camera - Camera}};
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(previousfog.projection_view));
|
||||
auto const previous{glm::vec3{previousfog.camera - Camera}};
|
||||
glProgramUniform3f(*m_inject_program, 5, previous.x, previous.y, previous.z);
|
||||
auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled};
|
||||
auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f};
|
||||
glProgramUniform4f(*m_inject_program, 6, share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f);
|
||||
glProgramUniform1i(*m_inject_program, 7, Cablamp);
|
||||
glProgramUniform2f(*m_inject_program, 8, m_previous_fog.slicing.x, m_previous_fog.slicing.z);
|
||||
glProgramUniform2f(*m_inject_program, 8, previousfog.slicing.x, previousfog.slicing.z);
|
||||
glProgramUniform2f(*m_inject_program, 9, jitter.x, jitter.y);
|
||||
m_inject_program->dispatch(groups(m_fog_grid.x), groups(m_fog_grid.y), static_cast<GLuint>(m_fog_grid.z));
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
@@ -565,50 +369,34 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_fog_current = next;
|
||||
m_fog_slicing = slicing;
|
||||
m_fog_timer.end();
|
||||
m_previous_fog = {projectionview, Camera, slicing, true};
|
||||
m_fog_volume_valid = true;
|
||||
previousfog = {projectionview, Camera, slicing, true};
|
||||
m_state.fog_made(slicing, m_fog_grid);
|
||||
}
|
||||
else
|
||||
m_previous_fog.valid = false;
|
||||
previousfog.valid = false;
|
||||
gl::program::unbind();
|
||||
}
|
||||
|
||||
void opengl46_weather::setup_pass(bool const Volume, glm::ivec2 const &Size)
|
||||
{
|
||||
auto const volume{Volume && m_fog_volume_valid && (Global.gfx_fog == 1)};
|
||||
m_weather.fog_volume_params = volume ? glm::vec4{1.f, m_fog_slicing.x, m_fog_slicing.z, static_cast<float>(m_fog_grid.z)} : glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target.x = 1.f / static_cast<float>(std::max(Size.x, 1));
|
||||
m_weather.fog_volume_target.y = 1.f / static_cast<float>(std::max(Size.y, 1));
|
||||
m_weather.fog_volume_target.z = volume ? m_fog_slicing.y : Global.gfx_fog_range;
|
||||
m_state.setup_pass(Volume, Size);
|
||||
upload_header();
|
||||
bind();
|
||||
}
|
||||
|
||||
void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, double const Siderealtime, double const Latitude)
|
||||
{
|
||||
if (!Global.gfx_stars || !m_stars_vao || m_stars_count == 0)
|
||||
if (!m_stars_vao || m_stars_count == 0)
|
||||
return;
|
||||
// the stars come out between the civil and the nautical twilight
|
||||
auto const night{1.f - glm::smoothstep(-12.f, -3.f, Sunangle)};
|
||||
if (night <= 0.f)
|
||||
glm::mat4 toworld, toview;
|
||||
glm::vec4 params;
|
||||
if (false == m_state.stars(Sunangle, Siderealtime, Latitude, View, toworld, toview, params))
|
||||
return;
|
||||
// the equatorial frame (z to the pole, x to the vernal equinox) to the sky of the observer (x west, y up, z north)
|
||||
auto const theta{glm::radians(Siderealtime)};
|
||||
auto const phi{glm::radians(Latitude)};
|
||||
auto const st{static_cast<float>(std::sin(theta))}, ct{static_cast<float>(std::cos(theta))};
|
||||
auto const sp{static_cast<float>(std::sin(phi))}, cp{static_cast<float>(std::cos(phi))};
|
||||
glm::mat3 equatorial;
|
||||
equatorial[0] = glm::vec3{st, cp * ct, -sp * ct};
|
||||
equatorial[1] = glm::vec3{-ct, cp * st, -sp * st};
|
||||
equatorial[2] = glm::vec3{0.f, sp, cp};
|
||||
auto const toworld{glm::mat4{equatorial}};
|
||||
auto const toview{glm::mat4{glm::mat3{View}}};
|
||||
m_stars_program->bind();
|
||||
glProgramUniformMatrix4fv(*m_stars_program, 0, 1, GL_FALSE, glm::value_ptr(toworld));
|
||||
glProgramUniformMatrix4fv(*m_stars_program, 1, 1, GL_FALSE, glm::value_ptr(toview));
|
||||
glProgramUniform4f(*m_stars_program, 2, STAR_BRIGHTNESS * Global.gfx_stars_brightness * night, static_cast<float>(std::fmod(Timer::GetTime(), 10000.0)), 2.5f, 0.f);
|
||||
glProgramUniform4fv(*m_stars_program, 2, 1, glm::value_ptr(params));
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
glBlendFunc(GL_ONE, GL_ONE);
|
||||
m_stars_vao->bind();
|
||||
@@ -620,9 +408,9 @@ void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, d
|
||||
void opengl46_weather::draw_sky(glm::mat4 const &Projection, glm::mat4 const &View, bool const Mainview)
|
||||
{
|
||||
int mode{0};
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
if (Mainview && m_clouds_view_valid)
|
||||
if (Mainview && m_state.clouds_view_valid())
|
||||
{
|
||||
mode = 1;
|
||||
glBindTextureUnit(gl::CLOUD_TEX, m_clouds_history[m_clouds_current]);
|
||||
@@ -674,28 +462,5 @@ std::string opengl46_weather::info_times()
|
||||
|
||||
void opengl46_weather::draw_debug_ui()
|
||||
{
|
||||
char const *fogmodes[]{"analytic (along the ray, as the old fog)", "volumetric"};
|
||||
ImGui::Combo("fog (gfx.fog)", &Global.gfx_fog, fogmodes, IM_ARRAYSIZE(fogmodes));
|
||||
char const *levels[]{"low", "medium", "high", "ultra"};
|
||||
auto fogquality{std::clamp(Global.gfx_fog_quality, 1, 4) - 1};
|
||||
if (ImGui::Combo("fog volume quality (gfx.fog.quality)", &fogquality, levels, IM_ARRAYSIZE(levels)))
|
||||
Global.gfx_fog_quality = fogquality + 1;
|
||||
ImGui::SliderFloat("fog volume depth, m (gfx.fog.range)", &Global.gfx_fog_range, 100.f, 10000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("lamps in the fog (gfx.fog.lights)", &Global.gfx_fog_lights, 0.f, 4.f);
|
||||
ImGui::Checkbox("shadows of the sun and headlights in the fog (gfx.fog.shadows)", &Global.gfx_fog_shadows);
|
||||
ImGui::Checkbox("fog blended over frames (gfx.fog.temporal)", &Global.gfx_fog_temporal);
|
||||
ImGui::SliderFloat("share of a new frame in the fog (gfx.fog.temporal.share)", &Global.gfx_fog_temporal_share, 0.02f, 1.f);
|
||||
char const *cloudlevels[]{"none", "low", "medium", "high", "ultra"};
|
||||
ImGui::Combo("clouds (gfx.clouds)", &Global.gfx_clouds, cloudlevels, IM_ARRAYSIZE(cloudlevels));
|
||||
ImGui::SliderFloat("cloud distance, m (gfx.clouds.distance)", &Global.gfx_clouds_distance, 5000.f, 200000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::Checkbox("cloud shadows (gfx.clouds.shadows)", &Global.gfx_clouds_shadows);
|
||||
ImGui::SliderFloat("cloud shadow map, m (gfx.clouds.shadows.range)", &Global.gfx_clouds_shadows_range, 1000.f, 100000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("cloud sunlight (tuning, not saved)", &m_cloud_sun_scale, 0.25f, 3.f);
|
||||
ImGui::SliderFloat("cloud sky light (tuning, not saved)", &m_cloud_sky_scale, 0.f, 3.f);
|
||||
ImGui::SliderFloat("cloud edge detail (tuning, not saved)", &m_cloud_detail_scale, 0.f, 3.f);
|
||||
ImGui::Checkbox("stars (gfx.stars)", &Global.gfx_stars);
|
||||
ImGui::SliderFloat("star brightness", &Global.gfx_stars_brightness, 0.f, 8.f);
|
||||
ImGui::Text("fog range %.0f m, sun share of the fog light %.2f, clouds let through %.2f on average", m_fog_range, m_sun_share, m_average_transmittance);
|
||||
ImGui::Separator();
|
||||
ui::weather_controls(simulation::Environment.weather());
|
||||
m_state.draw_debug_ui();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/vao.h"
|
||||
#include "gl/buffer.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/common/weatherstate.h"
|
||||
|
||||
class opengl46_sky;
|
||||
|
||||
@@ -64,22 +65,22 @@ class opengl46_weather
|
||||
// true when the volumetric clouds are drawn (the sky leaves its own overcast look then)
|
||||
bool clouds_drawn() const
|
||||
{
|
||||
return m_clouds_drawn;
|
||||
return m_state.clouds_drawn();
|
||||
}
|
||||
// true when the clouds cast shadows (the sunlight isn't dimmed by the overcast then, the shadows do it)
|
||||
bool cloud_shadows() const
|
||||
{
|
||||
return m_shadows_valid;
|
||||
return m_state.cloud_shadows();
|
||||
}
|
||||
// fog density of a regular draw (model_ubs.fog_density): 1 / the fog range
|
||||
float fog_density() const
|
||||
{
|
||||
return 1.f / m_fog_range;
|
||||
return m_state.fog_density();
|
||||
}
|
||||
// distance the fog hides things at, for the draw range: a dark object fades to about 2% there
|
||||
float fog_range() const
|
||||
{
|
||||
return m_fog_range;
|
||||
return m_state.fog_range();
|
||||
}
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
@@ -87,16 +88,7 @@ class opengl46_weather
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
struct quality_level
|
||||
{
|
||||
int divisor; // of the resolution of the clouds of the main view
|
||||
int steps; // samples through a layer at most
|
||||
int light_steps; // samples towards the sun
|
||||
bool detail; // with the small noise
|
||||
glm::ivec2 panorama; // size of the panorama
|
||||
glm::ivec3 fog_grid; // cells of the fog volume
|
||||
};
|
||||
static quality_level quality(int const Clouds, int const Fog);
|
||||
using quality_level = gfx::weather_state::quality_level;
|
||||
|
||||
static GLuint create_texture(GLenum Target, GLenum Format, glm::ivec3 const &Size, GLint Filter, GLint Wrap);
|
||||
static void release(GLuint &Texture);
|
||||
@@ -119,8 +111,8 @@ class opengl46_weather
|
||||
std::unique_ptr<gl::program> m_stars_program;
|
||||
|
||||
std::unique_ptr<gl::ubo> m_weather_ubo;
|
||||
gl::weather_ubs m_weather{};
|
||||
bool m_horizon_on_gpu{false};
|
||||
// the block and the decisions of the frame, shared with the vulkan renderer
|
||||
gfx::weather_state m_state;
|
||||
|
||||
// noise
|
||||
GLuint m_shape_noise{0};
|
||||
@@ -130,7 +122,6 @@ class opengl46_weather
|
||||
// shadows of the clouds
|
||||
GLuint m_shadow_map{0};
|
||||
int m_shadow_size{0};
|
||||
bool m_shadows_valid{false};
|
||||
gl::gpu_timer m_shadow_timer;
|
||||
|
||||
// clouds of the main view
|
||||
@@ -140,51 +131,21 @@ class opengl46_weather
|
||||
GLuint m_clouds_depth{0};
|
||||
std::array<GLuint, 2> m_clouds_history{0, 0};
|
||||
int m_clouds_current{0}; // the history holding the latest result
|
||||
bool m_clouds_view_valid{false}; // render_view() made the clouds of the frame
|
||||
gl::gpu_timer m_clouds_timer;
|
||||
// panorama
|
||||
GLuint m_panorama{0};
|
||||
glm::ivec2 m_panorama_size{0};
|
||||
int m_panorama_band{0};
|
||||
bool m_panorama_full{false}; // every band was drawn once since it was made
|
||||
gl::gpu_timer m_panorama_timer;
|
||||
bool m_clouds_drawn{false};
|
||||
|
||||
// fog volume
|
||||
glm::ivec3 m_fog_grid{0};
|
||||
std::array<GLuint, 2> m_fog_scattering{0, 0};
|
||||
int m_fog_current{0};
|
||||
GLuint m_fog_integrated{0};
|
||||
bool m_fog_volume_valid{false};
|
||||
float m_fog_range{5000.f};
|
||||
glm::vec4 m_fog_slicing{0.f};
|
||||
gl::gpu_timer m_fog_timer;
|
||||
|
||||
// the camera of the previous frame of the main view, for the blending over frames
|
||||
struct view_state
|
||||
{
|
||||
glm::mat4 projection_view{1.f}; // world axes to clip space, camera at the origin
|
||||
glm::dvec3 camera{0.0};
|
||||
glm::vec4 slicing{0.f};
|
||||
bool valid{false};
|
||||
};
|
||||
view_state m_previous_clouds;
|
||||
view_state m_previous_fog;
|
||||
int m_frame{0};
|
||||
|
||||
// stars
|
||||
std::optional<gl::vao> m_stars_vao;
|
||||
std::optional<gl::buffer> m_stars_buffer;
|
||||
int m_stars_count{0};
|
||||
|
||||
// tuning of the look of the clouds in the debug window, not saved: scales of their sunlight, of their sky light and of the
|
||||
// erosion of their edges by the detail noise (weather_sun_color.w, weather_ambient.w, cloud_wind.w)
|
||||
float m_cloud_sun_scale{1.f};
|
||||
float m_cloud_sky_scale{1.f};
|
||||
float m_cloud_detail_scale{1.f};
|
||||
// what the debug window shows
|
||||
float m_sun_share{0.f};
|
||||
float m_average_transmittance{1.f};
|
||||
std::string m_report;
|
||||
char m_report_input[256]{};
|
||||
};
|
||||
|
||||
214
rendering/vulkan14/vulkan14ao.cpp
Normal file
214
rendering/vulkan14/vulkan14ao.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
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 ambient occlusion of the vulkan renderer (the stage P3 of the plan, opengl46_ao and Render_ambient_occlusion() of the
|
||||
// opengl renderer): ground truth ambient occlusion at half the resolution in two compute kernels (gtao_main.comp,
|
||||
// gtao_denoise.comp) or the older ssao (postfx_ssao.frag and its blur), multiplied into the image of the scene
|
||||
// (postfx_ssao_apply.frag) with the fog in front of the surfaces taken into account
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// work group of the compute passes, 8x8 (gtao_*.comp)
|
||||
std::uint32_t const GROUP_SIZE{8};
|
||||
|
||||
struct gtao_constants
|
||||
{
|
||||
glm::mat4 inv_projection;
|
||||
glm::vec2 params;
|
||||
glm::vec2 pad;
|
||||
glm::vec4 clipw_row;
|
||||
glm::ivec2 quality;
|
||||
};
|
||||
static_assert(offsetof(gtao_constants, clipw_row) == 80, "the layout of the constants of gtao_main.comp");
|
||||
|
||||
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_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};
|
||||
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool vulkan14_renderer::init_ao(std::string &Reason)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto const device{ctx.device.handle()};
|
||||
// the noise of the older ssao: 4x4 random rotations in the plane of the surface, drawn after the hemisphere kernel the opengl
|
||||
// renderer makes with the same generator (the shader makes the kernel itself), so they are the same numbers
|
||||
{
|
||||
std::uniform_real_distribution<float> rnd(0.0f, 1.0f);
|
||||
std::default_random_engine gen(42); // NOSONAR fixed seed on purpose, the same as the opengl renderer
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
glm::vec3 s(rnd(gen) * 2.0f - 1.0f, rnd(gen) * 2.0f - 1.0f, rnd(gen));
|
||||
s = glm::normalize(s) * rnd(gen);
|
||||
}
|
||||
std::vector<glm::vec4> noise;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
noise.emplace_back(glm::normalize(glm::vec3(rnd(gen) * 2.0f - 1.0f, rnd(gen) * 2.0f - 1.0f, 0.0f)), 1.f);
|
||||
}
|
||||
vk14::image_desc desc;
|
||||
desc.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
desc.extent = {4, 4, 1};
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
scene.ssao_noise = std::make_shared<vk14::image>();
|
||||
if ((false == scene.ssao_noise->create(ctx.memory, desc, "ssao noise")) || (false == ctx.uploads.upload(scene.ssao_noise, noise.data(), noise.size() * sizeof(glm::vec4), {{0, {4, 4, 1}}}, false)))
|
||||
{
|
||||
Reason = "no image of the noise of the ssao";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
|
||||
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
|
||||
{
|
||||
Reason = std::string("no compute pipeline of ") + Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if ((false == kernel(scene.gtao_main, "gtao_main.comp", "gtao")) || (false == kernel(scene.gtao_denoise, "gtao_denoise.comp", "gtao denoise")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
scene.ao_apply = std::make_shared<vk14::scene_program>();
|
||||
scene.ao_apply->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
|
||||
scene.ao_apply->fragment = ctx.shaders.get("postfx_ssao_apply.frag");
|
||||
scene.ao_apply->fragment_name = "postfx_ssao_apply.frag";
|
||||
scene.ao_apply->vertices = vk14::vertex_kind::none;
|
||||
scene.ao_apply->name = "quad.vert + postfx_ssao_apply.frag";
|
||||
for (auto const *name : {"postfx_ssao.frag", "postfx_ssao_blur.frag"})
|
||||
{
|
||||
scene.post_fragments.emplace(name, ctx.shaders.get(name));
|
||||
}
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
Reason = std::string("invalid shader: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::destroy_ao()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto const device{ctx.device.handle()};
|
||||
scene.gtao_main.destroy(device);
|
||||
scene.gtao_denoise.destroy(device);
|
||||
scene.ao_apply.reset();
|
||||
scene.ssao_noise.reset();
|
||||
}
|
||||
|
||||
// the ambient occlusion of the viewport from the depth of its scene (resolved), multiplied into its colour
|
||||
// (opengl46_renderer::Render_ambient_occlusion()). Mode: Global.gfx_postfx_ao. outside a rendering
|
||||
void vulkan14_renderer::render_ao(std::size_t const Index, int const Mode)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &targets{scene.targets[Index]};
|
||||
auto const commands{ctx.commands};
|
||||
if ((false == bool(targets.ssao)) || (false == bool(targets.ssao_blur)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the projection the depth was drawn with
|
||||
auto const &projection{m_colorpass.pass_camera.projection()};
|
||||
auto const inverseprojection{glm::inverse(projection)};
|
||||
auto const &depth{*targets.depth};
|
||||
if (Mode == 1)
|
||||
{
|
||||
// the older ssao, which reads the projection from its constants
|
||||
vk14::draw_constants constants;
|
||||
for (int column = 0; column < 4; ++column)
|
||||
{
|
||||
constants.data[column] = projection[column];
|
||||
}
|
||||
post_pass("postfx_ssao.frag", *targets.ssao, {targets.depth.get(), scene.ssao_noise.get()}, constants, nullptr, 2u);
|
||||
post_pass("postfx_ssao_blur.frag", *targets.ssao_blur, {targets.ssao.get()}, vk14::draw_constants{});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const width{targets.ssao->desc().extent.width};
|
||||
auto const height{targets.ssao->desc().extent.height};
|
||||
auto const groups_x{(width + GROUP_SIZE - 1) / GROUP_SIZE};
|
||||
auto const groups_y{(height + GROUP_SIZE - 1) / GROUP_SIZE};
|
||||
// horizon search
|
||||
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)});
|
||||
gtao_constants constants{};
|
||||
constants.inv_projection = inverseprojection;
|
||||
constants.params = glm::vec2{Global.gfx_postfx_ao_radius, projection[0][0] * 0.5f * static_cast<float>(width)};
|
||||
constants.clipw_row = glm::vec4{projection[0][3], projection[1][3], projection[2][3], projection[3][3]};
|
||||
constants.quality = glm::ivec2{Global.gfx_postfx_ao_slices, Global.gfx_postfx_ao_steps};
|
||||
auto const depthimage{vk14::image_descriptor(ctx.sampling.clamp_nearest, depth.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)};
|
||||
scene.gtao_main.dispatch(commands, {depthimage, vk14::storage_image(targets.ssao->view())}, constants, groups_x, groups_y);
|
||||
// smoothing
|
||||
vk14::barriers(commands, {vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)});
|
||||
scene.gtao_denoise.dispatch(commands, {depthimage, vk14::image_descriptor(ctx.sampling.clamp_nearest, targets.ssao->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(targets.ssao_blur->view())},
|
||||
inverseprojection, groups_x, groups_y);
|
||||
}
|
||||
// result = image * occlusion, drawn over the colour of the scene with the pass set and the weather of its colour pass
|
||||
vk14::barriers(commands, {vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(*targets.colour, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
|
||||
VkRenderingAttachmentInfo attachment{};
|
||||
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachment.imageView = targets.colour->view();
|
||||
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
VkRenderingInfo rendering{};
|
||||
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
rendering.renderArea = {{0, 0}, targets.extent};
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
commands->begin_rendering(&rendering);
|
||||
auto &draw{*m_draw};
|
||||
auto const set{m_viewports[Index].main ? vk14::scene_state::set_colour : vk14::scene_state::set_plain};
|
||||
start_draws(targets.post_formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), set, false);
|
||||
draw.glare = true; // no depth
|
||||
draw.cullface = false;
|
||||
if (bind_scene_pipeline(*scene.ao_apply, targets.post_formats, vk14::blend_mode::multiply, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
|
||||
{
|
||||
apply_draw_state();
|
||||
draw.block = push_draw_block();
|
||||
if (draw.block != ~0u)
|
||||
{
|
||||
bind_pass_set();
|
||||
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
|
||||
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_nearest, scene.white->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
|
||||
images[0] = VkDescriptorImageInfo{ctx.sampling.clamp_nearest, depth.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
images[1] = VkDescriptorImageInfo{ctx.sampling.clamp_nearest, targets.ssao_blur->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
scene.material_set.push(commands, scene.scene_layout, images.data());
|
||||
vk14::draw_constants constants;
|
||||
constants.draw = draw.block;
|
||||
for (int column = 0; column < 4; ++column)
|
||||
{
|
||||
constants.data[column] = inverseprojection[column];
|
||||
}
|
||||
constants.data[4] = glm::vec4{(Mode == 1 ? 1.f : 0.f), Global.gfx_postfx_ao_strength, Global.gfx_postfx_ao_bounce, 0.f};
|
||||
push_constants(&constants);
|
||||
commands->draw(4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
commands->end_rendering();
|
||||
end_draws();
|
||||
}
|
||||
@@ -47,7 +47,31 @@ constexpr std::uint32_t CLUSTER_INDEX_SSBO = 6;
|
||||
constexpr std::uint32_t SHADOW_TEX = 7;
|
||||
constexpr std::uint32_t ENV_TEX = 8;
|
||||
constexpr std::uint32_t HEADLIGHT_TEX = 9;
|
||||
constexpr std::uint32_t PASS_BINDING_COUNT = 10;
|
||||
// the light of the sky (the stage P1): the directional part of the ambient light (gl::sky_data), the parameters of the
|
||||
// atmosphere (gl::atmosphere_ubs), its sky-view table, the environment map prefiltered with the GGX lobe
|
||||
constexpr std::uint32_t SKY_SSBO = 10;
|
||||
constexpr std::uint32_t ATMOSPHERE_UBO = 11;
|
||||
constexpr std::uint32_t SKY_VIEW_TEX = 12;
|
||||
constexpr std::uint32_t ENV_GGX_TEX = 13;
|
||||
// the weather (the stage P2): its block (gl::weather_ubs without the colours of the horizon, dynamic offset: it differs from
|
||||
// pass to pass), the colours of the fog towards the horizon (written by fog_horizon.comp or by the cpu), the fog volume of the
|
||||
// main view, the shadows of the clouds and the noises of the clouds
|
||||
constexpr std::uint32_t WEATHER_UBO = 14;
|
||||
constexpr std::uint32_t WEATHER_HORIZON_UBO = 15;
|
||||
constexpr std::uint32_t FOG_VOLUME_TEX = 16;
|
||||
constexpr std::uint32_t CLOUD_SHADOW_TEX = 17;
|
||||
constexpr std::uint32_t CLOUD_SHAPE_TEX = 18;
|
||||
constexpr std::uint32_t CLOUD_DETAIL_TEX = 19;
|
||||
constexpr std::uint32_t CLOUD_COVERAGE_TEX = 20;
|
||||
// the shadows of the lamps (the stage P4): the cube maps of the cab lamp and of the scenery lamps, the maps of the headlights
|
||||
constexpr std::uint32_t LOCAL_SHADOW_TEX = 21;
|
||||
constexpr std::uint32_t LOCAL_SHADOW_LAMPS_TEX = 22;
|
||||
constexpr std::uint32_t HEADLIGHT_SHADOW_TEX = 23;
|
||||
// the depth of the opaque scene drawn before its colour (the stage P3: the contact shadows; the soft particles of P6)
|
||||
constexpr std::uint32_t SCENE_DEPTH_TEX = 24;
|
||||
constexpr std::uint32_t PASS_BINDING_COUNT = 25;
|
||||
// the offsets of the bindings above with dynamic offsets, in the order of the bindings
|
||||
constexpr std::uint32_t PASS_DYNAMIC_OFFSETS = 6;
|
||||
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
|
||||
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
|
||||
|
||||
@@ -59,9 +83,16 @@ constexpr std::uint32_t CL_INDEX_SSBO = 3;
|
||||
constexpr std::uint32_t CL_COUNTER_SSBO = 4;
|
||||
constexpr std::uint32_t CL_LIGHT_SSBO = 5;
|
||||
|
||||
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on
|
||||
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on, and the exposure
|
||||
// worked out on the gpu (gl::exposure_data, the stage P1)
|
||||
constexpr std::uint32_t POST_SET = 0;
|
||||
constexpr std::uint32_t POST_TEXTURE_FIRST = 0;
|
||||
constexpr std::uint32_t POST_TEXTURES = 4;
|
||||
constexpr std::uint32_t POST_EXPOSURE_SSBO = 4;
|
||||
|
||||
// the compute kernels (vk14::compute_kernel): set 0 the pass set of the scene, set 1 pushed with the dispatch, its bindings
|
||||
// those the shader declares from 0 on
|
||||
constexpr std::uint32_t COMPUTE_SET = 1;
|
||||
|
||||
// the push constants of the graphics pipelines of the scene and of the post-processing (vk14::draw_constants): the draw, and
|
||||
// the parameters of the special shaders (the celestial bodies, the post-processing)
|
||||
|
||||
1004
rendering/vulkan14/vulkan14commands.cpp
Normal file
1004
rendering/vulkan14/vulkan14commands.cpp
Normal file
File diff suppressed because it is too large
Load Diff
186
rendering/vulkan14/vulkan14commands.h
Normal file
186
rendering/vulkan14/vulkan14commands.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 commands of a frame kept to be recorded into command buffers later (the stage M1 of the plan): the renderer makes the calls
|
||||
// of vkCmd* on a stream, which keeps them with their data in the order they were made. the stream is cut into chunks as it's
|
||||
// made, every so many draws: a chunk is recorded into a command buffer of its own, by a worker thread, and the command buffers go
|
||||
// to the queue in one submit in the order of the chunks. a chunk which starts inside a rendering begins it again resumed
|
||||
// (VK_RENDERING_RESUMING_BIT, the one before it ended it suspended) and sets again the state the draws before it left: the
|
||||
// pipelines, the descriptor sets, the pushed descriptors and constants, the vertex and index buffers and the dynamic state, as
|
||||
// the last calls of each kind made them. where the cuts are doesn't depend on the number of the threads, so the gpu gets the
|
||||
// same commands with any of them
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class command_stream
|
||||
{
|
||||
public:
|
||||
// the draws, dispatches and copies a chunk holds at least before it's cut (inside a rendering, between two draws)
|
||||
static constexpr std::uint32_t chunk_work{256};
|
||||
|
||||
// the calls, with the arguments vkCmd* takes (the command buffer left out); the data they point to is copied. chains of pNext
|
||||
// aren't kept: none of the calls is made with one
|
||||
void begin_rendering(VkRenderingInfo const *Info);
|
||||
void end_rendering();
|
||||
void pipeline_barrier2(VkDependencyInfo const *Dependency);
|
||||
void bind_pipeline(VkPipelineBindPoint BindPoint, VkPipeline Pipeline);
|
||||
void bind_descriptor_sets2(VkBindDescriptorSetsInfo const *Info);
|
||||
// Size: the bytes of the data of the template; BindPoint: the one the template was made for
|
||||
void push_descriptor_set_with_template2(VkPushDescriptorSetWithTemplateInfo const *Info, std::size_t Size, VkPipelineBindPoint BindPoint);
|
||||
void push_constants2(VkPushConstantsInfo const *Info);
|
||||
void bind_vertex_buffers(std::uint32_t First, std::uint32_t Count, VkBuffer const *Buffers, VkDeviceSize const *Offsets);
|
||||
void bind_index_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkIndexType Type);
|
||||
void draw(std::uint32_t Vertices, std::uint32_t Instances, std::uint32_t FirstVertex, std::uint32_t FirstInstance);
|
||||
void draw_indexed(std::uint32_t Indices, std::uint32_t Instances, std::uint32_t FirstIndex, std::int32_t VertexOffset, std::uint32_t FirstInstance);
|
||||
void draw_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride);
|
||||
void draw_indexed_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride);
|
||||
void dispatch(std::uint32_t X, std::uint32_t Y, std::uint32_t Z);
|
||||
void dispatch_indirect(VkBuffer Buffer, VkDeviceSize Offset);
|
||||
void set_viewport(std::uint32_t First, std::uint32_t Count, VkViewport const *Viewports);
|
||||
void set_scissor(std::uint32_t First, std::uint32_t Count, VkRect2D const *Scissors);
|
||||
void set_cull_mode(VkCullModeFlags Mode);
|
||||
void set_front_face(VkFrontFace Face);
|
||||
void set_primitive_topology(VkPrimitiveTopology Topology);
|
||||
void set_depth_test_enable(VkBool32 Enable);
|
||||
void set_depth_write_enable(VkBool32 Enable);
|
||||
void set_depth_compare_op(VkCompareOp Op);
|
||||
void set_depth_bias_enable(VkBool32 Enable);
|
||||
void set_depth_bias(float Constant, float Clamp, float Slope);
|
||||
void set_primitive_restart_enable(VkBool32 Enable);
|
||||
void set_stencil_test_enable(VkBool32 Enable);
|
||||
void fill_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, std::uint32_t Data);
|
||||
void update_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, void const *Data);
|
||||
void copy_buffer2(VkCopyBufferInfo2 const *Info);
|
||||
void copy_image2(VkCopyImageInfo2 const *Info);
|
||||
void copy_buffer_to_image2(VkCopyBufferToImageInfo2 const *Info);
|
||||
void copy_image_to_buffer2(VkCopyImageToBufferInfo2 const *Info);
|
||||
void blit_image2(VkBlitImageInfo2 const *Info);
|
||||
void clear_color_image(VkImage Image, VkImageLayout Layout, VkClearColorValue const *Color, std::uint32_t Count, VkImageSubresourceRange const *Ranges);
|
||||
void clear_depth_stencil_image(VkImage Image, VkImageLayout Layout, VkClearDepthStencilValue const *Value, std::uint32_t Count, VkImageSubresourceRange const *Ranges);
|
||||
void write_timestamp2(VkPipelineStageFlags2 Stage, VkQueryPool Pool, std::uint32_t Query);
|
||||
void reset_query_pool(VkQueryPool Pool, std::uint32_t First, std::uint32_t Count);
|
||||
void begin_query(VkQueryPool Pool, std::uint32_t Query, VkQueryControlFlags Flags);
|
||||
void end_query(VkQueryPool Pool, std::uint32_t Query);
|
||||
void begin_debug_utils_label(VkDebugUtilsLabelEXT const *Label);
|
||||
void end_debug_utils_label();
|
||||
|
||||
// the stream is made anew
|
||||
void clear();
|
||||
bool empty() const
|
||||
{
|
||||
return m_chunks.empty() || (m_data.empty());
|
||||
}
|
||||
// the stream is ended: no calls after it
|
||||
void finish();
|
||||
// the chunks, valid after finish()
|
||||
std::size_t chunks() const
|
||||
{
|
||||
return m_chunks.size();
|
||||
}
|
||||
// the calls recorded since the start
|
||||
std::size_t calls() const
|
||||
{
|
||||
return m_calls;
|
||||
}
|
||||
// the chunk recorded into Buffer (begun already, ended by the caller)
|
||||
void replay(std::size_t Chunk, VkCommandBuffer Buffer) const;
|
||||
// all of it into one command buffer
|
||||
void replay(VkCommandBuffer Buffer) const;
|
||||
|
||||
private:
|
||||
// the calls of the state a chunk sets again; the dynamic states have a slot each
|
||||
enum state_slot : std::uint32_t
|
||||
{
|
||||
pipeline_graphics,
|
||||
pipeline_compute,
|
||||
viewport,
|
||||
scissor,
|
||||
cull_mode,
|
||||
front_face,
|
||||
topology,
|
||||
depth_test,
|
||||
depth_write,
|
||||
depth_compare,
|
||||
depth_bias_enable,
|
||||
depth_bias,
|
||||
primitive_restart,
|
||||
stencil_test,
|
||||
index_buffer,
|
||||
sets_graphics, // set_slots of each
|
||||
sets_compute = sets_graphics + 8,
|
||||
pushed_graphics = sets_compute + 8,
|
||||
pushed_compute = pushed_graphics + 8,
|
||||
vertex_buffers = pushed_compute + 8, // vertex_slots
|
||||
slot_count = vertex_buffers + 8
|
||||
};
|
||||
static constexpr std::size_t no_call{~static_cast<std::size_t>(0)};
|
||||
static constexpr std::uint32_t set_slots{8};
|
||||
static constexpr std::uint32_t vertex_slots{8};
|
||||
struct chunk
|
||||
{
|
||||
std::size_t first{0}; // offset of its first call in the data
|
||||
std::size_t end{0}; // past its last one
|
||||
// the begin of the rendering the chunk starts inside of, resumed, and the calls of the state set again, in their order
|
||||
std::size_t resumed{no_call};
|
||||
std::vector<std::size_t> state;
|
||||
// the begin of the rendering the chunk ends inside of, suspended
|
||||
std::size_t suspended{no_call};
|
||||
};
|
||||
|
||||
// room for a call of Type with Size bytes of its own data after its header, aligned; the offset of the call
|
||||
std::size_t add(std::uint32_t Type, std::size_t Size);
|
||||
template <typename Type_> Type_ &payload(std::size_t Call)
|
||||
{
|
||||
return *reinterpret_cast<Type_ *>(m_data.data() + Call + header_size);
|
||||
}
|
||||
template <typename Type_> Type_ const &payload(std::size_t Call) const
|
||||
{
|
||||
return *reinterpret_cast<Type_ const *>(m_data.data() + Call + header_size);
|
||||
}
|
||||
template <typename Type_> Type_ const *at(std::size_t Offset) const
|
||||
{
|
||||
return reinterpret_cast<Type_ const *>(m_data.data() + Offset);
|
||||
}
|
||||
// copies Bytes of Data after the call made last (aligned), returns their offset in the data
|
||||
std::size_t append(void const *Data, std::size_t Bytes);
|
||||
// the call made last ends here
|
||||
void close(std::size_t Call);
|
||||
void remember(std::uint32_t Slot, std::size_t Call)
|
||||
{
|
||||
m_state[Slot] = Call;
|
||||
}
|
||||
// a draw, dispatch or copy was added: the chunk is cut after it when it has enough of them
|
||||
void work();
|
||||
void cut();
|
||||
// Flags: added to those of a begin of a rendering
|
||||
void execute(std::size_t Call, VkCommandBuffer Buffer, VkRenderingFlags Flags = 0) const;
|
||||
|
||||
static constexpr std::size_t header_size{8};
|
||||
std::vector<std::uint8_t> m_data;
|
||||
std::vector<chunk> m_chunks;
|
||||
std::array<std::size_t, slot_count> m_state{};
|
||||
// the pushes of the constants: the last one of each range and stages, in their order
|
||||
std::vector<std::size_t> m_pushes;
|
||||
std::size_t m_rendering{no_call}; // the begin of the rendering under way
|
||||
std::uint32_t m_queries{0}; // queries active
|
||||
std::uint32_t m_work{0}; // of the chunk under way
|
||||
std::size_t m_calls{0};
|
||||
bool m_finished{false};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
namespace vk14
|
||||
@@ -143,7 +144,7 @@ void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::
|
||||
vkSetDebugUtilsObjectNameEXT(Device, &info);
|
||||
}
|
||||
|
||||
void begin_label(VkCommandBuffer Commands, char const *Name)
|
||||
void begin_label(command_stream *Commands, char const *Name)
|
||||
{
|
||||
if ((false == debug_utils_enabled) || (vkCmdBeginDebugUtilsLabelEXT == nullptr))
|
||||
{
|
||||
@@ -152,16 +153,16 @@ void begin_label(VkCommandBuffer Commands, char const *Name)
|
||||
VkDebugUtilsLabelEXT label{};
|
||||
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
|
||||
label.pLabelName = Name;
|
||||
vkCmdBeginDebugUtilsLabelEXT(Commands, &label);
|
||||
Commands->begin_debug_utils_label(&label);
|
||||
}
|
||||
|
||||
void end_label(VkCommandBuffer Commands)
|
||||
void end_label(command_stream *Commands)
|
||||
{
|
||||
if ((false == debug_utils_enabled) || (vkCmdEndDebugUtilsLabelEXT == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
vkCmdEndDebugUtilsLabelEXT(Commands);
|
||||
Commands->end_debug_utils_label();
|
||||
}
|
||||
|
||||
void log_info(std::string const &Message)
|
||||
|
||||
@@ -22,6 +22,9 @@ extern "C" GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance insta
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// the commands of a frame, recorded later into command buffers (vulkan14commands.h)
|
||||
class command_stream;
|
||||
|
||||
// human readable VkResult, for the log
|
||||
char const *result_string(VkResult Result);
|
||||
// "1.4.309"
|
||||
@@ -48,8 +51,8 @@ template <typename Handle_> std::uint64_t handle_value(Handle_ Handle)
|
||||
// a name of an object for the messages of the validation layer and for captures; nothing without debug utils
|
||||
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name);
|
||||
// a label of a part of the commands (a pass), for captures and the messages of the validation layer
|
||||
void begin_label(VkCommandBuffer Commands, char const *Name);
|
||||
void end_label(VkCommandBuffer Commands);
|
||||
void begin_label(command_stream *Commands, char const *Name);
|
||||
void end_label(command_stream *Commands);
|
||||
|
||||
// the log of the renderer, with the "vulkan: " prefix
|
||||
void log_info(std::string const &Message);
|
||||
|
||||
395
rendering/vulkan14/vulkan14compute.cpp
Normal file
395
rendering/vulkan14/vulkan14compute.cpp
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14compute.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
std::vector<VkDescriptorSetLayoutBinding> reflect_bindings(std::vector<std::uint32_t> const &Spirv, std::uint32_t const Set, VkShaderStageFlags const Stages)
|
||||
{
|
||||
std::uint32_t constexpr op_constant{43};
|
||||
std::uint32_t constexpr op_type_image{25};
|
||||
std::uint32_t constexpr op_type_sampler{26};
|
||||
std::uint32_t constexpr op_type_sampled_image{27};
|
||||
std::uint32_t constexpr op_type_array{28};
|
||||
std::uint32_t constexpr op_type_runtime_array{29};
|
||||
std::uint32_t constexpr op_type_struct{30};
|
||||
std::uint32_t constexpr op_type_pointer{32};
|
||||
std::uint32_t constexpr op_variable{59};
|
||||
std::uint32_t constexpr op_decorate{71};
|
||||
std::uint32_t constexpr decoration_block{2};
|
||||
std::uint32_t constexpr decoration_buffer_block{3};
|
||||
std::uint32_t constexpr decoration_binding{33};
|
||||
std::uint32_t constexpr decoration_set{34};
|
||||
std::uint32_t constexpr storage_uniform_constant{0};
|
||||
std::uint32_t constexpr storage_uniform{2};
|
||||
std::uint32_t constexpr storage_storage_buffer{12};
|
||||
|
||||
struct type_info
|
||||
{
|
||||
std::uint32_t opcode{0};
|
||||
std::uint32_t operand1{0}; // pointer: the type pointed to; array: the element; sampled image: the image
|
||||
std::uint32_t operand2{0}; // pointer: the storage class; array: the length; image: sampled
|
||||
std::uint32_t dim{0};
|
||||
};
|
||||
std::unordered_map<std::uint32_t, type_info> types;
|
||||
std::unordered_map<std::uint32_t, std::uint32_t> constants;
|
||||
std::unordered_map<std::uint32_t, std::uint32_t> sets;
|
||||
std::unordered_map<std::uint32_t, std::uint32_t> bindingsof;
|
||||
std::unordered_map<std::uint32_t, std::uint32_t> blocks; // struct -> decoration
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> variables; // id, pointer type
|
||||
|
||||
for (std::size_t word = 5; word < Spirv.size();)
|
||||
{
|
||||
auto const count{Spirv[word] >> 16};
|
||||
auto const opcode{Spirv[word] & 0xffff};
|
||||
if ((count == 0) || (word + count > Spirv.size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto const *op{&Spirv[word]};
|
||||
switch (opcode)
|
||||
{
|
||||
case op_decorate:
|
||||
if (count >= 3)
|
||||
{
|
||||
if ((op[2] == decoration_set) && (count >= 4))
|
||||
{
|
||||
sets[op[1]] = op[3];
|
||||
}
|
||||
else if ((op[2] == decoration_binding) && (count >= 4))
|
||||
{
|
||||
bindingsof[op[1]] = op[3];
|
||||
}
|
||||
else if ((op[2] == decoration_block) || (op[2] == decoration_buffer_block))
|
||||
{
|
||||
blocks[op[1]] = op[2];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case op_constant:
|
||||
if (count >= 4)
|
||||
{
|
||||
constants[op[2]] = op[3];
|
||||
}
|
||||
break;
|
||||
case op_type_image:
|
||||
if (count >= 9)
|
||||
{
|
||||
types[op[1]] = {opcode, 0, op[7], op[3]};
|
||||
}
|
||||
break;
|
||||
case op_type_sampler:
|
||||
types[op[1]] = {opcode, 0, 0, 0};
|
||||
break;
|
||||
case op_type_sampled_image:
|
||||
types[op[1]] = {opcode, op[2], 0, 0};
|
||||
break;
|
||||
case op_type_array:
|
||||
types[op[1]] = {opcode, op[2], op[3], 0};
|
||||
break;
|
||||
case op_type_runtime_array:
|
||||
types[op[1]] = {opcode, op[2], 0, 0};
|
||||
break;
|
||||
case op_type_struct:
|
||||
types[op[1]] = {opcode, 0, 0, 0};
|
||||
break;
|
||||
case op_type_pointer:
|
||||
types[op[1]] = {opcode, op[3], op[2], 0};
|
||||
break;
|
||||
case op_variable:
|
||||
if (count >= 4)
|
||||
{
|
||||
variables.emplace_back(op[2], op[1]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
word += count;
|
||||
}
|
||||
|
||||
std::map<std::uint32_t, VkDescriptorSetLayoutBinding> result;
|
||||
for (auto const &[id, pointertype] : variables)
|
||||
{
|
||||
auto const set{sets.find(id)};
|
||||
auto const binding{bindingsof.find(id)};
|
||||
if ((set == sets.end()) || (binding == bindingsof.end()) || (set->second != Set))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const pointer{types.find(pointertype)};
|
||||
if (pointer == types.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const storage{pointer->second.operand2};
|
||||
auto type{pointer->second.operand1};
|
||||
std::uint32_t descriptors{1};
|
||||
auto lookup{types.find(type)};
|
||||
if ((lookup != types.end()) && (lookup->second.opcode == op_type_array))
|
||||
{
|
||||
auto const length{constants.find(lookup->second.operand2)};
|
||||
descriptors = (length != constants.end() ? length->second : 1);
|
||||
type = lookup->second.operand1;
|
||||
lookup = types.find(type);
|
||||
}
|
||||
if (lookup == types.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto descriptortype{VK_DESCRIPTOR_TYPE_MAX_ENUM};
|
||||
switch (lookup->second.opcode)
|
||||
{
|
||||
case op_type_sampled_image:
|
||||
descriptortype = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
break;
|
||||
case op_type_image:
|
||||
descriptortype = (lookup->second.operand2 == 2 ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
|
||||
break;
|
||||
case op_type_sampler:
|
||||
descriptortype = VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
break;
|
||||
case op_type_struct:
|
||||
{
|
||||
auto const block{blocks.find(type)};
|
||||
if (storage == storage_storage_buffer)
|
||||
{
|
||||
descriptortype = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
}
|
||||
else if (storage == storage_uniform)
|
||||
{
|
||||
descriptortype = ((block != blocks.end()) && (block->second == decoration_buffer_block) ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if ((descriptortype == VK_DESCRIPTOR_TYPE_MAX_ENUM) || ((storage != storage_uniform_constant) && (storage != storage_uniform) && (storage != storage_storage_buffer)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
result[binding->second] = VkDescriptorSetLayoutBinding{binding->second, descriptortype, descriptors, Stages, nullptr};
|
||||
}
|
||||
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
||||
for (auto const &entry : result)
|
||||
{
|
||||
bindings.push_back(entry.second);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
bool compute_kernel::create(VkDevice Device, pipeline_library &Pipelines, std::shared_ptr<shader_code const> const &Shader, VkDescriptorSetLayout Passlayout, std::vector<std::uint32_t> const &Specialization, std::string const &Name)
|
||||
{
|
||||
m_name = Name;
|
||||
auto const bindings{reflect_bindings(Shader->spirv, COMPUTE_PUSH_SET, VK_SHADER_STAGE_COMPUTE_BIT)};
|
||||
for (std::size_t i = 0; i < bindings.size(); ++i)
|
||||
{
|
||||
// the data of the template is read in the order of the bindings, one descriptor each
|
||||
if ((bindings[i].binding != i) || (bindings[i].descriptorCount != 1))
|
||||
{
|
||||
log_error("the compute shader of " + Name + " has to take the bindings of its pushed set one by one from 0");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_count = bindings.size();
|
||||
std::vector<VkDescriptorSetLayout> sets{Passlayout};
|
||||
if (m_count > 0)
|
||||
{
|
||||
if (false == m_set.create(Device, bindings, COMPUTE_PUSH_SET, VK_PIPELINE_BIND_POINT_COMPUTE, sizeof(descriptor), Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
sets.push_back(m_set.layout);
|
||||
}
|
||||
VkPushConstantRange const constants{VK_SHADER_STAGE_COMPUTE_BIT, 0, COMPUTE_PUSH_CONSTANTS};
|
||||
m_layout = make_pipeline_layout(Device, sets, {constants}, Name);
|
||||
if (m_layout == VK_NULL_HANDLE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((m_count > 0) && (false == m_set.create_template(Device, m_layout, Name)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_pipeline = Pipelines.compute(Shader, m_layout, Specialization, Name);
|
||||
return m_pipeline != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void compute_kernel::destroy(VkDevice Device)
|
||||
{
|
||||
m_set.destroy(Device);
|
||||
if (m_layout != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyPipelineLayout(Device, m_layout, nullptr);
|
||||
m_layout = VK_NULL_HANDLE;
|
||||
}
|
||||
// the pipeline belongs to the pipeline library
|
||||
m_pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void compute_kernel::bind(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size) const
|
||||
{
|
||||
Commands->bind_pipeline(VK_PIPELINE_BIND_POINT_COMPUTE, m_pipeline);
|
||||
if ((m_count > 0) && (Descriptors != nullptr))
|
||||
{
|
||||
m_set.push(Commands, m_layout, Descriptors);
|
||||
}
|
||||
if ((Size > 0) && (Constants != nullptr))
|
||||
{
|
||||
VkPushConstantsInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
|
||||
info.layout = m_layout;
|
||||
info.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
info.offset = 0;
|
||||
info.size = Size;
|
||||
info.pValues = Constants;
|
||||
Commands->push_constants2(&info);
|
||||
}
|
||||
}
|
||||
|
||||
void compute_kernel::dispatch(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size, std::uint32_t const X, std::uint32_t const Y, std::uint32_t const Z) const
|
||||
{
|
||||
if ((m_pipeline == VK_NULL_HANDLE) || (X == 0) || (Y == 0) || (Z == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
bind(Commands, Descriptors, Constants, Size);
|
||||
Commands->dispatch(X, Y, Z);
|
||||
}
|
||||
|
||||
void compute_kernel::dispatch_indirect(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size, VkBuffer Buffer, VkDeviceSize const Offset) const
|
||||
{
|
||||
if (m_pipeline == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bind(Commands, Descriptors, Constants, Size);
|
||||
Commands->dispatch_indirect(Buffer, Offset);
|
||||
}
|
||||
|
||||
void memory_barrier(command_stream *Commands, VkPipelineStageFlags2 const SrcStage, VkAccessFlags2 const SrcAccess, VkPipelineStageFlags2 const DstStage, VkAccessFlags2 const DstAccess)
|
||||
{
|
||||
VkMemoryBarrier2 barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
|
||||
barrier.srcStageMask = SrcStage;
|
||||
barrier.srcAccessMask = SrcAccess;
|
||||
barrier.dstStageMask = DstStage;
|
||||
barrier.dstAccessMask = DstAccess;
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.memoryBarrierCount = 1;
|
||||
dependency.pMemoryBarriers = &barrier;
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
|
||||
void compute_barrier(command_stream *Commands)
|
||||
{
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT |
|
||||
VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
|
||||
// the buffer written with the commands of the frame, between its reads by the shaders of the frames before and after
|
||||
void update_buffer(command_stream *Commands, buffer const &Target, void const *Data, VkDeviceSize const Size, VkDeviceSize const Offset)
|
||||
{
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
Commands->update_buffer(Target.handle(), Offset, Size, Data);
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
}
|
||||
|
||||
// readback
|
||||
|
||||
bool readback_ring::create(allocator const &Allocator, VkDeviceSize const Size, std::uint32_t const Frames, std::string const &Name)
|
||||
{
|
||||
m_size = Size;
|
||||
m_slots.clear();
|
||||
m_slots.resize(Frames);
|
||||
for (std::uint32_t i = 0; i < Frames; ++i)
|
||||
{
|
||||
if (false == m_slots[i].data.create(Allocator, Size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::readback, Name + " " + std::to_string(i)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void readback_ring::destroy()
|
||||
{
|
||||
m_slots.clear();
|
||||
}
|
||||
|
||||
void readback_ring::push(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value, VkBuffer Source, VkDeviceSize const Offset, VkDeviceSize const Size)
|
||||
{
|
||||
if (Slot >= m_slots.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &slot{m_slots[Slot]};
|
||||
VkBufferCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
|
||||
region.srcOffset = Offset;
|
||||
region.dstOffset = 0;
|
||||
region.size = std::min(Size, m_size);
|
||||
VkCopyBufferInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
|
||||
copy.srcBuffer = Source;
|
||||
copy.dstBuffer = slot.data.handle();
|
||||
copy.regionCount = 1;
|
||||
copy.pRegions = ®ion;
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
Commands->copy_buffer2(©);
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_HOST_BIT, VK_ACCESS_2_HOST_READ_BIT);
|
||||
slot.value = Value;
|
||||
slot.pending = true;
|
||||
}
|
||||
|
||||
bool readback_ring::pop(std::uint64_t const Completed, void *Data, VkDeviceSize const Size)
|
||||
{
|
||||
slot *newest{nullptr};
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
if (slot.pending && (slot.value <= Completed) && ((newest == nullptr) || (slot.value > newest->value)))
|
||||
{
|
||||
newest = &slot;
|
||||
}
|
||||
}
|
||||
if (newest == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
if (slot.pending && (slot.value <= newest->value))
|
||||
{
|
||||
slot.pending = false;
|
||||
}
|
||||
}
|
||||
auto const size{std::min(Size, m_size)};
|
||||
newest->data.invalidate(0, size);
|
||||
std::memcpy(Data, newest->data.mapped(), static_cast<std::size_t>(size));
|
||||
m_popped = newest->value;
|
||||
m_poppedslot = static_cast<std::uint32_t>(newest - m_slots.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
156
rendering/vulkan14/vulkan14compute.h
Normal file
156
rendering/vulkan14/vulkan14compute.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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 compute passes of the vulkan renderer (the stages P of the plan): a kernel is a compute shader of shaders/vk14 with its
|
||||
// pipeline. its layout is the pass set of the scene (set 0, the persistent one, for the shaders which read the lights, the
|
||||
// clusters, the shadow maps or the weather of the pass) and a pushed set of its own (set 1), whose bindings are read from the
|
||||
// SPIR-V of the shader, with push constants for the parameters the opengl renderer gives its compute programs as loose
|
||||
// uniforms. the data of the gpu read back by the cpu goes through a readback ring, a buffer seen by the cpu for each frame in
|
||||
// flight, taken once the gpu is done with the frame
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14shaders.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// what a template of a pushed set reads for each of its bindings: an image or a buffer, laid out alike
|
||||
union descriptor
|
||||
{
|
||||
VkDescriptorImageInfo image;
|
||||
VkDescriptorBufferInfo buffer;
|
||||
};
|
||||
static_assert(sizeof(VkDescriptorImageInfo) == sizeof(VkDescriptorBufferInfo), "the images and the buffers of the templates are laid out by one stride");
|
||||
|
||||
inline descriptor image_descriptor(VkSampler Sampler, VkImageView View, VkImageLayout Layout)
|
||||
{
|
||||
descriptor result;
|
||||
result.image = VkDescriptorImageInfo{Sampler, View, Layout};
|
||||
return result;
|
||||
}
|
||||
inline descriptor storage_image(VkImageView View)
|
||||
{
|
||||
descriptor result;
|
||||
result.image = VkDescriptorImageInfo{VK_NULL_HANDLE, View, VK_IMAGE_LAYOUT_GENERAL};
|
||||
return result;
|
||||
}
|
||||
inline descriptor buffer_descriptor(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Range)
|
||||
{
|
||||
descriptor result;
|
||||
result.buffer = VkDescriptorBufferInfo{Buffer, Offset, Range};
|
||||
return result;
|
||||
}
|
||||
|
||||
// the bindings of the descriptor set Set the shader uses, from the decorations of its variables, in the order of the bindings
|
||||
std::vector<VkDescriptorSetLayoutBinding> reflect_bindings(std::vector<std::uint32_t> const &Spirv, std::uint32_t Set, VkShaderStageFlags Stages);
|
||||
|
||||
// the set of the compute kernels pushed with their dispatches
|
||||
constexpr std::uint32_t COMPUTE_PUSH_SET = 1;
|
||||
// the push constants of the kernels at most, vulkan 1.4 guarantees them
|
||||
constexpr std::uint32_t COMPUTE_PUSH_CONSTANTS = 256;
|
||||
|
||||
class compute_kernel
|
||||
{
|
||||
public:
|
||||
compute_kernel() = default;
|
||||
compute_kernel(compute_kernel const &) = delete;
|
||||
compute_kernel &operator=(compute_kernel const &) = delete;
|
||||
|
||||
// the kernel of the shader: Passlayout is the layout of the pass set of the scene, set 0 of every kernel; the bindings of
|
||||
// set 1 are those the shader declares. Specialization: the values of the constants 0, 1, 2 ...
|
||||
bool create(VkDevice Device, pipeline_library &Pipelines, std::shared_ptr<shader_code const> const &Shader, VkDescriptorSetLayout Passlayout, std::vector<std::uint32_t> const &Specialization, std::string const &Name);
|
||||
void destroy(VkDevice Device);
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_pipeline != VK_NULL_HANDLE;
|
||||
}
|
||||
VkPipelineLayout layout() const
|
||||
{
|
||||
return m_layout;
|
||||
}
|
||||
std::size_t bindings() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
// the pipeline bound, the set pushed (Descriptors in the order of the bindings of the shader), the constants pushed (Size
|
||||
// bytes) and the groups dispatched
|
||||
void dispatch(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const;
|
||||
template <typename Constants> void dispatch(command_stream *Commands, std::vector<descriptor> const &Descriptors, Constants const &Data, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const
|
||||
{
|
||||
static_assert(sizeof(Constants) <= COMPUTE_PUSH_CONSTANTS, "too many push constants");
|
||||
dispatch(Commands, Descriptors.data(), &Data, static_cast<std::uint32_t>(sizeof(Constants)), X, Y, Z);
|
||||
}
|
||||
// the same without push constants
|
||||
void run(command_stream *Commands, std::vector<descriptor> const &Descriptors, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const
|
||||
{
|
||||
dispatch(Commands, Descriptors.data(), nullptr, 0, X, Y, Z);
|
||||
}
|
||||
// the same with the groups taken from a buffer
|
||||
void dispatch_indirect(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size, VkBuffer Buffer, VkDeviceSize Offset) const;
|
||||
|
||||
private:
|
||||
void bind(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size) const;
|
||||
|
||||
push_set m_set;
|
||||
VkPipelineLayout m_layout{VK_NULL_HANDLE};
|
||||
VkPipeline m_pipeline{VK_NULL_HANDLE};
|
||||
std::size_t m_count{0};
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
// a global memory barrier, between the passes which write and read buffers (the images have their own, vk14::barriers())
|
||||
void memory_barrier(command_stream *Commands, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess);
|
||||
// the writes of the compute passes made seen by what follows: the compute passes, the draws, the indirect commands, the copies
|
||||
void compute_barrier(command_stream *Commands);
|
||||
// Size bytes of Data (a multiple of 4, at most 64 KiB) written to the buffer from Offset with the commands of the frame, between
|
||||
// its reads by the shaders before and after. outside a rendering
|
||||
void update_buffer(command_stream *Commands, buffer const &Target, void const *Data, VkDeviceSize Size, VkDeviceSize Offset);
|
||||
|
||||
// data copied from the gpu for the cpu: a buffer per frame in flight, the copy of a frame read once the gpu is done with it
|
||||
class readback_ring
|
||||
{
|
||||
public:
|
||||
bool create(allocator const &Allocator, VkDeviceSize Size, std::uint32_t Frames, std::string const &Name);
|
||||
void destroy();
|
||||
// the copy of Size bytes (at most the size of the ring) of Source from Offset, in the frame of the slot, done by the gpu once
|
||||
// it reaches Value of the timeline
|
||||
void push(command_stream *Commands, std::uint32_t Slot, std::uint64_t Value, VkBuffer Source, VkDeviceSize Offset, VkDeviceSize Size);
|
||||
// the newest copy the gpu is done with (Completed: the value of the timeline it reached), copied to Data; false when there's
|
||||
// none newer than the one taken last
|
||||
bool pop(std::uint64_t Completed, void *Data, VkDeviceSize Size);
|
||||
// the frame of the copy taken last, for the data which tell what they were made for
|
||||
std::uint64_t popped_value() const
|
||||
{
|
||||
return m_popped;
|
||||
}
|
||||
std::uint32_t popped_slot() const
|
||||
{
|
||||
return m_poppedslot;
|
||||
}
|
||||
|
||||
private:
|
||||
struct slot
|
||||
{
|
||||
buffer data;
|
||||
std::uint64_t value{0};
|
||||
bool pending{false};
|
||||
};
|
||||
std::vector<slot> m_slots;
|
||||
VkDeviceSize m_size{0};
|
||||
std::uint64_t m_popped{0};
|
||||
std::uint32_t m_poppedslot{0};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -25,11 +25,23 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/vulkan14/vulkan14imgui.h"
|
||||
#include "rendering/vulkan14/vulkan14bindings.h"
|
||||
#include "rendering/vulkan14/vulkan14geometry.h"
|
||||
#include "rendering/vulkan14/vulkan14compute.h"
|
||||
#include "rendering/vulkan14/vulkan14cull.h"
|
||||
#include "rendering/vulkan14/vulkan14shadows.h"
|
||||
#include "rendering/vulkan14/vulkan14terrain.h"
|
||||
#include "rendering/vulkan14/vulkan14grass.h"
|
||||
#include "rendering/vulkan14/vulkan14particles.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "utilities/workerpool.h"
|
||||
#include "model/material.h"
|
||||
#include "gl/ubo.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@@ -42,8 +54,10 @@ enum class vertex_kind : std::uint8_t
|
||||
none, // made by the vertex shader (the celestial bodies, the full screen quads)
|
||||
scene, // gfx::basic_vertex and the user data, the geometry banks
|
||||
colour, // position and colour, the sky dome
|
||||
star, // direction with the magnitude and colour with the phase of the twinkling in one stream, the stars
|
||||
particle, // position, colour and texture coordinates in one stream, the smoke
|
||||
position_uv // position and texture coordinates in two streams, the precipitation
|
||||
position_uv, // position and texture coordinates in two streams, the precipitation
|
||||
particle_instance // three vec4 of gl::particle_draw for each instance, the particles of the gpu
|
||||
};
|
||||
|
||||
// the push constants of the pipelines of the scene and of the post-processing (shaders/vk14/common.glsl, draw_constants_block)
|
||||
@@ -65,6 +79,12 @@ struct scene_program : public gfx::shader_program
|
||||
// the fragment shader of the passes without the velocity target (NO_MOTION_OUTPUT), made when it's first needed
|
||||
std::string fragment_name;
|
||||
std::shared_ptr<shader_code const> fragment_single;
|
||||
// the vertex shader, and the definitions both stages were made with
|
||||
std::string vertex_name;
|
||||
std::string defines;
|
||||
// the variant of the multi draws of the instances drawn from the gpu culling (MODEL_DRAWS), made when it's first needed
|
||||
std::shared_ptr<scene_program> multidraw;
|
||||
bool multidraw_tried{false};
|
||||
vertex_kind vertices{vertex_kind::scene};
|
||||
std::unordered_map<std::uint64_t, VkPipeline> pipelines;
|
||||
};
|
||||
@@ -76,6 +96,8 @@ struct target_formats
|
||||
std::uint32_t colours{0};
|
||||
VkFormat depth{VK_FORMAT_UNDEFINED};
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
// the views drawn at once (multiview), 0: none
|
||||
std::uint32_t view_mask{0};
|
||||
// the number the pipelines of the targets are known by
|
||||
std::uint32_t id{0};
|
||||
};
|
||||
@@ -102,6 +124,19 @@ struct viewport_targets
|
||||
std::shared_ptr<image> output;
|
||||
// the depth under the cursor of the editor: one sample of the depth of the ground resolved to it, and the buffer it's read back with
|
||||
std::shared_ptr<image> cursor_depth;
|
||||
// the depth of the opaque scene drawn before its colour, which the contact shadows read (the stage P3), and the formats of the
|
||||
// pass drawing it (the depth alone)
|
||||
std::shared_ptr<image> scene_depth;
|
||||
target_formats depth_formats;
|
||||
// the ambient occlusion (gfx.postfx.ao, the stage P3), half the size: the raw one and the smoothed one; made when it's on
|
||||
std::shared_ptr<image> ssao;
|
||||
std::shared_ptr<image> ssao_blur;
|
||||
// the formats of the passes drawn over the colour of the scene after it (the ambient occlusion): the colour alone
|
||||
target_formats post_formats;
|
||||
// the bloom chain (gfx.postfx.bloom, the stage P1): the levels down, half the size each, and up, one fewer
|
||||
std::vector<std::shared_ptr<image>> bloom_down;
|
||||
std::vector<std::shared_ptr<image>> bloom_up;
|
||||
std::vector<glm::ivec2> bloom_sizes;
|
||||
target_formats formats;
|
||||
target_formats output_formats;
|
||||
bool srgb{true}; // the output encodes the colour (gfx.shadergamma no)
|
||||
@@ -129,6 +164,16 @@ struct cluster_state
|
||||
bool stale{true}; // the boxes have to be made anew
|
||||
VkDeviceSize params_offset{0}; // in the ring of the frame, for the pass under way
|
||||
std::uint32_t lights_total{0};
|
||||
// the counters of the cullings read back, for the debug panel (gl::cluster_grid::cull_stats): entries of the index list in
|
||||
// use, lights dropped from full clusters, the most of them seen
|
||||
readback_ring counters_readback;
|
||||
struct
|
||||
{
|
||||
std::uint32_t index_count{0};
|
||||
std::uint32_t overflow_count{0};
|
||||
std::uint32_t overflow_peak{0};
|
||||
bool valid{false};
|
||||
} stats;
|
||||
};
|
||||
|
||||
// a picking or a reading of a value of the depth, waiting for the gpu to be done with the frame it was made in
|
||||
@@ -140,6 +185,104 @@ struct readback
|
||||
glm::ivec2 position{0};
|
||||
};
|
||||
|
||||
// the light of the sky on the gpu (the stage P1, opengl46_sky): the buffers the shading reads (the directional ambient light,
|
||||
// the parameters of the atmosphere), the look-up tables of the atmosphere with the kernels making them and the result read
|
||||
// back, the environment map prefiltered with the GGX lobe, the stars
|
||||
struct sky_state
|
||||
{
|
||||
buffer sky; // gl::sky_data
|
||||
buffer atmosphere; // gl::atmosphere_ubs
|
||||
buffer result; // gl::atmosphere_result, written by atmosphere_ambient.comp
|
||||
readback_ring result_readback;
|
||||
std::shared_ptr<image> transmittance;
|
||||
std::shared_ptr<image> multiscattering;
|
||||
std::shared_ptr<image> skyview;
|
||||
bool tables_ready{false}; // the tables were made once (they are black before)
|
||||
compute_kernel transmittance_kernel;
|
||||
compute_kernel multiscattering_kernel;
|
||||
compute_kernel skyview_kernel;
|
||||
compute_kernel ambient_kernel;
|
||||
// the prefiltered environment map, a cube view of each of its levels for the prefilter to write
|
||||
std::shared_ptr<image> ggx;
|
||||
std::vector<VkImageView> ggx_levels;
|
||||
bool ggx_ready{false};
|
||||
compute_kernel prefilter_kernel;
|
||||
// what the buffers were last given
|
||||
gl::sky_data uploaded{};
|
||||
bool sky_uploaded{false};
|
||||
// the dome drawn with the atmosphere, the stars of the catalogue
|
||||
std::shared_ptr<scene_program> atmosphere_dome;
|
||||
std::shared_ptr<scene_program> stars;
|
||||
std::shared_ptr<buffer> star_vertices;
|
||||
std::uint32_t star_count{0};
|
||||
};
|
||||
|
||||
// the automatic exposure (the stage P1, opengl46_exposure): the histogram and the exposure on the gpu (gl::exposure_data), the
|
||||
// kernels, the values read back for the debug window
|
||||
struct exposure_state
|
||||
{
|
||||
buffer data;
|
||||
compute_kernel histogram;
|
||||
compute_kernel average;
|
||||
readback_ring readback;
|
||||
float values[4]{1.f, 1.f, 1.f, 0.f};
|
||||
bool cleared{false};
|
||||
};
|
||||
|
||||
// the weather on the gpu (the stage P2, opengl46_weather): the noises of the clouds, the map of their shadows, the clouds of the
|
||||
// main view and their history, the panorama of the clouds for the other views, the fog volume of the main view and its
|
||||
// history, with the kernels making them, and the program laying the clouds and the fog over the sky
|
||||
struct weather_resources
|
||||
{
|
||||
std::shared_ptr<image> shape_noise;
|
||||
std::shared_ptr<image> detail_noise;
|
||||
std::shared_ptr<image> coverage_noise;
|
||||
bool noise_made{false};
|
||||
std::shared_ptr<image> shadow_map;
|
||||
int shadow_size{0};
|
||||
// the clouds of the main view, at a part of its resolution
|
||||
glm::ivec2 view_size{0};
|
||||
std::shared_ptr<image> clouds_color;
|
||||
std::shared_ptr<image> clouds_depth;
|
||||
std::array<std::shared_ptr<image>, 2> clouds_history;
|
||||
int clouds_current{0}; // the history holding the latest result
|
||||
std::shared_ptr<image> panorama;
|
||||
glm::ivec2 panorama_size{0};
|
||||
// what the panorama is marched with as its depth, which it doesn't write
|
||||
std::shared_ptr<image> depth_dummy;
|
||||
// the fog volume: the scattering of the cells and its history, the fog integrated along the view
|
||||
glm::ivec3 fog_grid{0};
|
||||
std::array<std::shared_ptr<image>, 2> fog_scattering;
|
||||
int fog_current{0};
|
||||
std::shared_ptr<image> fog_integrated;
|
||||
compute_kernel noise;
|
||||
compute_kernel march;
|
||||
compute_kernel temporal;
|
||||
compute_kernel shadow;
|
||||
compute_kernel horizon;
|
||||
compute_kernel inject;
|
||||
compute_kernel integrate;
|
||||
std::shared_ptr<scene_program> sky;
|
||||
};
|
||||
|
||||
// the gpu culling of the instances (the stage P5, Global.gfx_gpucull): the cullings of the passes, which share the records and
|
||||
// the kernels with the one of the main view, and the depth pyramid of its occlusion test
|
||||
struct cull_resources
|
||||
{
|
||||
instance_cull main;
|
||||
std::array<instance_cull, 3> cascades;
|
||||
instance_cull reflections;
|
||||
// the maps of headlights, by the slot of the map, and the scenery parts of the maps of lamps, by their order among those drawn
|
||||
// in a frame: the passes of a frame don't share the buffers of a culling. made when first needed
|
||||
std::vector<std::unique_ptr<instance_cull>> headlights;
|
||||
std::vector<std::unique_ptr<instance_cull>> lamps;
|
||||
hiz_pyramid hiz;
|
||||
bool ready{false};
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm), made when they're first needed
|
||||
shadow_fitter shadowfit;
|
||||
bool shadowfit_tried{false};
|
||||
};
|
||||
|
||||
// what the scene is drawn with
|
||||
struct scene_state
|
||||
{
|
||||
@@ -163,6 +306,8 @@ struct scene_state
|
||||
};
|
||||
std::array<VkDescriptorSet, set_count> pass_sets{};
|
||||
bool pass_sets_stale{true};
|
||||
// the pass sets were made anew this many times (the ones of the cullings follow them)
|
||||
std::uint64_t pass_generation{0};
|
||||
// the rings of the draws: the blocks of the models (gl::model_ubs) and the modelview matrices of the instances
|
||||
window_ring draws;
|
||||
window_ring instances;
|
||||
@@ -178,10 +323,48 @@ struct scene_state
|
||||
std::array<VkImageView, 6> env_faces{};
|
||||
std::shared_ptr<image> pick_colour;
|
||||
std::shared_ptr<image> pick_depth;
|
||||
// the stand-ins of the images of the stages P the pass sets hold until there are real ones: a volume (the fog volume, the noises
|
||||
// of the clouds), the cube shadow maps of the lamps, the shadow maps of the headlights, the depth of the scene
|
||||
std::shared_ptr<image> volume_empty;
|
||||
std::shared_ptr<image> cube_shadow_empty;
|
||||
std::shared_ptr<image> headlight_shadow_empty;
|
||||
std::shared_ptr<image> depth_empty;
|
||||
// the colours of the fog towards the horizon (the stage P2), a block of the pass sets
|
||||
buffer weather_horizon;
|
||||
VkImageView headlights_view{VK_NULL_HANDLE}; // the one the pass sets were made with
|
||||
VkSampler headlights_sampler{VK_NULL_HANDLE};
|
||||
// the views of the images of the bindings of each pass set it was made with (vulkan14scene.cpp, pass_set_images())
|
||||
std::array<std::array<VkImageView, bindings::PASS_BINDING_COUNT>, 3> pass_images{};
|
||||
cluster_state clusters;
|
||||
// the ambient occlusion: the noise of the older ssao, the kernels of the ground truth one, the program applying it
|
||||
std::shared_ptr<image> ssao_noise;
|
||||
compute_kernel gtao_main;
|
||||
compute_kernel gtao_denoise;
|
||||
std::shared_ptr<scene_program> ao_apply;
|
||||
sky_state sky;
|
||||
exposure_state exposure;
|
||||
weather_resources weather;
|
||||
cull_resources cull;
|
||||
// the shadow maps of the lamps and the headlights (the stage P4), and the programs drawing the cube maps
|
||||
local_shadow_maps lamps;
|
||||
std::shared_ptr<scene_program> cube_shadow, cube_alpha_shadow;
|
||||
target_formats cube_formats;
|
||||
// the materials of the heightmap terrain (the stage P6), made with the bank of the textures
|
||||
std::unique_ptr<terrain_materials> terrain;
|
||||
// the grass of the heightmap terrain, and its program, made when it's first drawn
|
||||
grass_field grass;
|
||||
std::shared_ptr<scene_program> grass_program;
|
||||
bool grass_tried{false};
|
||||
// the smoke and the rain or snow of the gpu
|
||||
gpu_particles particles;
|
||||
// the heatmap of the light clusters (gfx.lights.clusterheatmap), made when it's first drawn
|
||||
std::shared_ptr<scene_program> cluster_heatmap;
|
||||
bool cluster_heatmap_tried{false};
|
||||
// the targets of the viewports, [0] the main window
|
||||
std::vector<viewport_targets> targets;
|
||||
// the format of the levels of the bloom chains: no alpha, no negative values, half the memory of rgba16f, where the device
|
||||
// draws to it
|
||||
VkFormat bloom_format{VK_FORMAT_B10G11R11_UFLOAT_PACK32};
|
||||
// the formats of the passes which don't depend on the viewport
|
||||
target_formats shadow_formats;
|
||||
target_formats reflection_formats;
|
||||
@@ -190,6 +373,9 @@ struct scene_state
|
||||
// the programs: of the materials (by the name Fetch_Shader() is given), and the ones of the renderer
|
||||
std::unordered_map<std::string, std::shared_ptr<scene_program>> materials;
|
||||
std::shared_ptr<scene_program> shadow, alpha_shadow, pick, pick_surface, line, billboard, freespot, celestial, skydome, smoke, precipitation;
|
||||
// the depth prepass of the scene (the stage P3): without and with the alpha test, and which of them a program of a material takes
|
||||
std::shared_ptr<scene_program> prepass, prepass_alpha;
|
||||
std::unordered_map<gfx::shader_program const *, int> prepass_classes;
|
||||
// the post-processing, by the fragment shader
|
||||
std::unordered_map<std::string, std::shared_ptr<shader_code const>> post_fragments;
|
||||
std::shared_ptr<shader_code const> quad;
|
||||
@@ -225,7 +411,20 @@ struct vulkan14_renderer::context
|
||||
VkCommandPool pool{VK_NULL_HANDLE};
|
||||
// the uploads of the frame go to the queue first, then the commands of the frame
|
||||
VkCommandBuffer uploads{VK_NULL_HANDLE};
|
||||
VkCommandBuffer commands{VK_NULL_HANDLE};
|
||||
// the calls made while the frame is made, recorded into the command buffers once it's done (M1): the uploads into theirs,
|
||||
// the commands chunk by chunk by the workers
|
||||
vk14::command_stream upload_stream;
|
||||
vk14::command_stream commands;
|
||||
// a command pool for each worker, with the command buffers it made
|
||||
struct worker_commands
|
||||
{
|
||||
VkCommandPool pool{VK_NULL_HANDLE};
|
||||
std::vector<VkCommandBuffer> buffers;
|
||||
std::size_t used{0};
|
||||
};
|
||||
std::vector<worker_commands> workers;
|
||||
// the command buffers of the chunks of the frame, in their order
|
||||
std::vector<VkCommandBuffer> chunks;
|
||||
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
|
||||
std::uint64_t done{0};
|
||||
// the number of the frame, for the report of a lost device
|
||||
@@ -262,7 +461,35 @@ struct vulkan14_renderer::context
|
||||
bool devicelost{false};
|
||||
// the frame being recorded: its index among the frames in flight, its commands
|
||||
std::uint32_t slot{0};
|
||||
VkCommandBuffer commands{VK_NULL_HANDLE};
|
||||
vk14::command_stream *commands{nullptr};
|
||||
// the threads recording the command buffers of the frames: a pool of its own, the one of the application is for the simulation
|
||||
worker_pool workers;
|
||||
// the cpu time the command buffers of the last frame took to record, in milliseconds; its calls and chunks
|
||||
std::atomic<double> record_time{0.0};
|
||||
std::atomic<std::size_t> record_calls{0};
|
||||
std::atomic<std::size_t> record_chunks{0};
|
||||
std::atomic<std::size_t> record_threads{0};
|
||||
// what's left of a frame made by the main thread: recording its command buffers, submitting them, presenting its windows
|
||||
struct render_job
|
||||
{
|
||||
frame *target{nullptr};
|
||||
bool uploaded{false};
|
||||
std::vector<VkSemaphoreSubmitInfo> waits;
|
||||
std::vector<VkSemaphoreSubmitInfo> signals;
|
||||
// the windows presented once the frame is submitted (the render thread)
|
||||
std::vector<vk14::swapchain *> windows;
|
||||
};
|
||||
// the render thread and the job it's given
|
||||
std::thread render_thread;
|
||||
std::mutex render_mutex;
|
||||
std::condition_variable render_wake;
|
||||
std::condition_variable render_idle;
|
||||
render_job render_pending;
|
||||
bool render_queued{false}; // render_pending holds a job not done yet
|
||||
bool render_stop{false};
|
||||
char const *render_failure{nullptr};
|
||||
// how long the main thread waited for the render thread at the start of the last frame, in milliseconds
|
||||
double render_wait{0.0};
|
||||
|
||||
~context()
|
||||
{
|
||||
@@ -270,6 +497,26 @@ struct vulkan14_renderer::context
|
||||
}
|
||||
|
||||
bool create_frames(std::uint32_t const Count, std::string &Reason);
|
||||
// the streams of Frame recorded into its command buffers; false: no command buffer for them
|
||||
bool record(frame &Frame, bool Uploads);
|
||||
|
||||
// the job done; false: Failure says where it failed
|
||||
bool execute(render_job &Job, char const *&Failure);
|
||||
// the windows of the frame submitted last presented; false: the device or a surface is lost
|
||||
bool present(std::vector<vk14::swapchain *> const &Windows);
|
||||
// the render thread (gfx.vulkan.renderthread, M4): the frames are recorded, submitted and presented by it while the main
|
||||
// thread goes on with the simulation of the next one. the main thread waits for it to be done before it begins a frame and
|
||||
// before anything else touches the queue, the swapchains or the device as a whole
|
||||
void start_render_thread();
|
||||
void stop_render_thread();
|
||||
bool render_threaded() const
|
||||
{
|
||||
return render_thread.joinable();
|
||||
}
|
||||
// the job given to the render thread
|
||||
void hand_over(render_job &&Job);
|
||||
// waits until the render thread is done with the jobs given to it; where it failed, if it did
|
||||
char const *drain();
|
||||
// the parts of the renderer made once the device is there
|
||||
bool create_core(std::uint32_t const Frames, std::string &Reason);
|
||||
// the value of the timeline the gpu reached, 0 when it can't be read
|
||||
@@ -282,7 +529,7 @@ struct vulkan14_renderer::context
|
||||
// the state of the draws of the walk being recorded into a pass of the scene
|
||||
struct vulkan14_renderer::draw_state
|
||||
{
|
||||
VkCommandBuffer commands{VK_NULL_HANDLE};
|
||||
vk14::command_stream *commands{nullptr};
|
||||
vk14::target_formats const *targets{nullptr};
|
||||
VkExtent2D extent{0, 0};
|
||||
vk14::scene_state::pass_set_kind set{vk14::scene_state::set_plain};
|
||||
@@ -292,6 +539,7 @@ struct vulkan14_renderer::draw_state
|
||||
std::uint32_t scene_offset{0};
|
||||
std::uint32_t light_offset{0};
|
||||
std::uint32_t cluster_offset{0};
|
||||
std::uint32_t weather_offset{0};
|
||||
bool set_dirty{true};
|
||||
// the program and the textures for the next draw (walk_material(), walk_program(), walk_texture())
|
||||
vk14::scene_program *program{nullptr};
|
||||
@@ -319,4 +567,20 @@ struct vulkan14_renderer::draw_state
|
||||
std::uint32_t query{~0u}; // the query under way
|
||||
// the draws made in the pass, for the debug panel
|
||||
std::uint32_t draws{0};
|
||||
// the depth of the scene before its colour (the stage P3): the walk draws with the programs of the depth prepass, and what they
|
||||
// don't draw is left out (depthskip, set by walk_material())
|
||||
bool depthpass{false};
|
||||
bool depthskip{true};
|
||||
// the pass sets of a culling, the instance buffer its output, for the indirect draws of its instances; null: the pass sets
|
||||
VkDescriptorSet const *cull_sets{nullptr};
|
||||
// the rendering under way, begun again where it was after it was ended for the compute passes of the culling
|
||||
struct rendering_resume
|
||||
{
|
||||
std::array<VkRenderingAttachmentInfo, 2> colours{};
|
||||
VkRenderingAttachmentInfo depth{};
|
||||
bool has_depth{false};
|
||||
VkRenderingInfo info{};
|
||||
std::vector<vk14::image_use> uses;
|
||||
bool valid{false};
|
||||
} resume;
|
||||
};
|
||||
|
||||
604
rendering/vulkan14/vulkan14cull.cpp
Normal file
604
rendering/vulkan14/vulkan14cull.cpp
Normal file
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14cull.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
|
||||
#include "rendering/frustum.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr VkDeviceSize RECORD_SIZE{sizeof(scene::basic_cell::instance_record)};
|
||||
// the counters read back: the header and the entries compared
|
||||
constexpr VkDeviceSize READBACK_SIZE{(gl::INSTANCE_COUNTER_HEADER + 16384) * sizeof(std::uint32_t)};
|
||||
|
||||
// the parameters of instance_cull.comp, its kernel_block (std140)
|
||||
struct cull_block
|
||||
{
|
||||
glm::mat4 view;
|
||||
glm::vec4 planes[6];
|
||||
glm::vec4 distance_params;
|
||||
glm::vec4 size_params;
|
||||
glm::mat4 hiz_projection;
|
||||
glm::ivec4 hiz_params;
|
||||
glm::vec4 hiz_screen;
|
||||
glm::uvec4 phases;
|
||||
};
|
||||
static_assert(sizeof(cull_block) == 304, "the block of the culling doesn't match the shader");
|
||||
|
||||
// the parameters of hiz_build.comp
|
||||
struct hiz_constants
|
||||
{
|
||||
glm::ivec4 sizes;
|
||||
glm::ivec4 params;
|
||||
};
|
||||
|
||||
// what read and wrote the buffers of the culling before (the draws of the passes before, the copies for the cpu, the kernels) is
|
||||
// done before they're written again
|
||||
void before_writes(command_stream *Commands)
|
||||
{
|
||||
memory_barrier(Commands,
|
||||
VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
instance_cull::~instance_cull() = default;
|
||||
|
||||
bool instance_cull::init(cull_environment const &Environment, VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, instance_cull const *Shared)
|
||||
{
|
||||
if (Shared != nullptr)
|
||||
{
|
||||
m_shared = Shared->m_shared;
|
||||
share_records(*Shared);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto shared{std::make_shared<shared_data>()};
|
||||
shared->environment = Environment;
|
||||
try
|
||||
{
|
||||
if ((false == shared->cull.create(Device, Pipelines, Shaders.get("instance_cull.comp"), Passlayout, {}, "instance cull")) ||
|
||||
(false == shared->offsets.create(Device, Pipelines, Shaders.get("instance_cull_offsets.comp"), Passlayout, {}, "instance cull offsets")) ||
|
||||
(false == shared->commands.create(Device, Pipelines, Shaders.get("instance_cull_commands.comp"), Passlayout, {}, "instance cull commands")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (gfx::shader_exception const &Exception)
|
||||
{
|
||||
log_error(Exception.what());
|
||||
return false;
|
||||
}
|
||||
shared->records = std::make_shared<buffer>();
|
||||
if (false == shared->records->create(*Environment.memory, 65536 * RECORD_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, "instance records"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_shared = std::move(shared);
|
||||
}
|
||||
auto const &environment{m_shared->environment};
|
||||
return reserve(m_counters, READBACK_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "instance counters") &&
|
||||
reserve(m_output, 65536 * sizeof(glm::mat4), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "instance output") &&
|
||||
reserve(m_commands, 3 * 4096 * sizeof(gl::instance_draw_command), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, "instance commands") &&
|
||||
m_readback.create(*environment.memory, READBACK_SIZE, environment.frames, "instance counters readback");
|
||||
}
|
||||
|
||||
void instance_cull::destroy(VkDevice Device)
|
||||
{
|
||||
if (m_shared && (m_shared.use_count() == 1))
|
||||
{
|
||||
m_shared->cull.destroy(Device);
|
||||
m_shared->offsets.destroy(Device);
|
||||
m_shared->commands.destroy(Device);
|
||||
}
|
||||
m_shared.reset();
|
||||
m_counters.reset();
|
||||
m_output.reset();
|
||||
m_commands.reset();
|
||||
m_readback.destroy();
|
||||
if (set_pool != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyDescriptorPool(Device, set_pool, nullptr);
|
||||
set_pool = VK_NULL_HANDLE;
|
||||
}
|
||||
sets = {};
|
||||
}
|
||||
|
||||
void instance_cull::begin_frame(std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Completed)
|
||||
{
|
||||
if (!m_shared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &environment{m_shared->environment};
|
||||
environment.slot = Slot;
|
||||
environment.value = Value;
|
||||
environment.completed = Completed;
|
||||
}
|
||||
|
||||
bool instance_cull::reserve(std::shared_ptr<buffer> &Buffer, VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name)
|
||||
{
|
||||
if (Buffer && (Size <= Buffer->size()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto const &environment{m_shared->environment};
|
||||
auto replacement{std::make_shared<buffer>()};
|
||||
if (false == replacement->create(*environment.memory, std::max<VkDeviceSize>(Size, Buffer ? 2 * Buffer->size() : 0), Usage, memory_kind::device, Name))
|
||||
{
|
||||
log_error(std::string("no memory for the buffer of the culling of the instances: ") + Name);
|
||||
return false;
|
||||
}
|
||||
if (Buffer)
|
||||
{
|
||||
// the frames in flight may still read it
|
||||
environment.deletions->push(environment.uploads->current_value(), [old = std::move(Buffer)]() {});
|
||||
}
|
||||
Buffer = std::move(replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::int64_t instance_cull::record_buffer_size() const
|
||||
{
|
||||
return (m_shared && m_shared->records) ? static_cast<std::int64_t>(m_shared->records->size()) : 0;
|
||||
}
|
||||
|
||||
void instance_cull::resize_record_buffer(std::int64_t const Size)
|
||||
{
|
||||
auto &shared{*m_shared};
|
||||
auto const &environment{shared.environment};
|
||||
auto replacement{std::make_shared<buffer>()};
|
||||
if (false == replacement->create(*environment.memory, static_cast<VkDeviceSize>(Size), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, "instance records"))
|
||||
{
|
||||
log_error("no memory for the records of the instances");
|
||||
return;
|
||||
}
|
||||
// the passes culled before in the frame keep the old buffer, with its records
|
||||
environment.deletions->push(environment.uploads->current_value(), [old = std::move(shared.records)]() {});
|
||||
shared.records = std::move(replacement);
|
||||
shared.pending.clear();
|
||||
shared.parts.clear();
|
||||
}
|
||||
|
||||
void instance_cull::restart_records()
|
||||
{
|
||||
// the records of the passes culled before in the frame go to the gpu before the frame (the uploads), where the records sent from
|
||||
// the start of the buffer again would take their places
|
||||
resize_record_buffer(record_buffer_size());
|
||||
}
|
||||
|
||||
void instance_cull::send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count)
|
||||
{
|
||||
if ((Count == 0) || !m_shared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &shared{*m_shared};
|
||||
auto const *data{reinterpret_cast<std::uint8_t const *>(Records)};
|
||||
shared.pending.insert(shared.pending.end(), data, data + Count * RECORD_SIZE);
|
||||
shared.parts.emplace_back(First * RECORD_SIZE, Count * RECORD_SIZE);
|
||||
}
|
||||
|
||||
void instance_cull::flush_records()
|
||||
{
|
||||
auto &shared{*m_shared};
|
||||
if (shared.pending.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (false == shared.environment.uploads->upload_buffer(shared.records, shared.pending.data(), shared.pending.size(), shared.parts))
|
||||
{
|
||||
log_error("no staging memory for the records of the instances");
|
||||
}
|
||||
shared.pending.clear();
|
||||
shared.parts.clear();
|
||||
}
|
||||
|
||||
void instance_cull::push_counts()
|
||||
{
|
||||
// copied with the last stage of the culling (read_counters())
|
||||
}
|
||||
|
||||
bool instance_cull::pop_counts(std::uint32_t *Counts)
|
||||
{
|
||||
if (!m_shared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_readback.pop(m_shared->environment.completed, Counts, READBACK_SIZE);
|
||||
}
|
||||
|
||||
void instance_cull::read_counters(command_stream *Commands)
|
||||
{
|
||||
auto const &environment{m_shared->environment};
|
||||
m_readback.push(Commands, environment.slot, environment.value, m_counters->handle(), 0, READBACK_SIZE);
|
||||
}
|
||||
|
||||
std::vector<descriptor> instance_cull::cull_descriptors(cull_occlusion const *Occlusion) const
|
||||
{
|
||||
auto const &environment{m_shared->environment};
|
||||
auto const slice = [](ring_slice const &Slice) { return buffer_descriptor(Slice.buffer, Slice.offset, Slice.size); };
|
||||
return {buffer_descriptor(m_shared->records->handle(), 0, VK_WHOLE_SIZE),
|
||||
slice(m_jobs),
|
||||
slice(m_groups),
|
||||
slice(m_lods),
|
||||
buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE),
|
||||
buffer_descriptor(m_output->handle(), 0, VK_WHOLE_SIZE),
|
||||
((Occlusion != nullptr) && (Occlusion->pyramid != VK_NULL_HANDLE)) ? image_descriptor(Occlusion->sampler, Occlusion->pyramid, VK_IMAGE_LAYOUT_GENERAL) : environment.no_pyramid,
|
||||
slice(m_block)};
|
||||
}
|
||||
|
||||
void instance_cull::dispatch(command_stream *Commands, glm::mat4 const &View, cull_occlusion const *Occlusion, bool const Twophase)
|
||||
{
|
||||
if (!m_shared || (false == dispatch_begin()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &shared{*m_shared};
|
||||
auto const &environment{shared.environment};
|
||||
flush_records();
|
||||
|
||||
m_countersize = (gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t);
|
||||
if ((false == reserve(m_counters, std::max(m_countersize, READBACK_SIZE), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "instance counters")) ||
|
||||
(false == reserve(m_output, static_cast<VkDeviceSize>(std::max<std::uint32_t>(m_candidates, 1)) * sizeof(glm::mat4), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "instance output")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the data of the pass in the ring of the frame
|
||||
auto const place = [&](ring_slice &Slice, void const *Data, std::size_t const Size, VkDeviceSize const Alignment) {
|
||||
Slice = environment.ring->allocate(std::max<VkDeviceSize>(Size, 16), Alignment);
|
||||
if (Slice && (Size > 0))
|
||||
{
|
||||
std::memcpy(Slice.data, Data, Size);
|
||||
}
|
||||
return bool(Slice);
|
||||
};
|
||||
cull_block block{};
|
||||
block.view = View;
|
||||
frustum_planes(block.planes);
|
||||
block.distance_params = glm::vec4{m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange};
|
||||
block.size_params = glm::vec4{m_setup.zoom, 0.f, 0.f, 0.f};
|
||||
// the occlusion test only when the instances are drawn from the result, the cpu doesn't make it
|
||||
m_occlusion = (m_setup.drawing && (Occlusion != nullptr) && (Occlusion->pyramid != VK_NULL_HANDLE));
|
||||
if (m_occlusion)
|
||||
{
|
||||
block.hiz_projection = Occlusion->projection;
|
||||
block.hiz_params = glm::ivec4{1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y};
|
||||
block.hiz_screen = glm::vec4{Occlusion->screen, 0.f, 0.f};
|
||||
}
|
||||
m_twophase = (m_occlusion && Twophase);
|
||||
block.phases = glm::uvec4{m_twophase ? 1u : 0u, 0u, 0u, 0u};
|
||||
if ((false == place(m_jobs, m_joblist.data(), m_joblist.size() * sizeof(gl::instance_cull_job), environment.storage_alignment)) ||
|
||||
(false == place(m_groups, m_grouplist.data(), m_grouplist.size() * sizeof(gl::instance_cull_group), environment.storage_alignment)) ||
|
||||
(false == place(m_lods, m_lodlist.data(), m_lodlist.size() * sizeof(float), environment.storage_alignment)) || (false == place(m_block, &block, sizeof(block), environment.uniform_alignment)))
|
||||
{
|
||||
log_error("the ring of the frame is full, the instances aren't culled");
|
||||
m_occlusion = false;
|
||||
m_twophase = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// counts and cursors start at zero
|
||||
before_writes(Commands);
|
||||
std::uint32_t const header[gl::INSTANCE_COUNTER_HEADER]{m_setup.frame, 0, m_entries, 0, 0, 0, 0, 0};
|
||||
Commands->update_buffer(m_counters->handle(), 0, sizeof(header), header);
|
||||
Commands->fill_buffer(m_counters->handle(), sizeof(header), m_countersize - sizeof(header), 0);
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_CLEAR_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
|
||||
// one invocation per record; past the count limit of one dimension the groups go into rows
|
||||
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
|
||||
auto const groups_x{std::min<std::uint32_t>(groups, 65535)};
|
||||
auto const groups_y{(groups + groups_x - 1) / groups_x};
|
||||
auto const descriptors{cull_descriptors(Occlusion)};
|
||||
auto const jobs{static_cast<std::uint32_t>(m_joblist.size())};
|
||||
shared.cull.dispatch(Commands, descriptors, glm::uvec4{jobs, m_candidates, m_entries, 0u}, groups_x, groups_y);
|
||||
compute_barrier(Commands);
|
||||
shared.offsets.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_entries, 0u, 0u, 0u}, 1);
|
||||
compute_barrier(Commands);
|
||||
shared.cull.dispatch(Commands, descriptors, glm::uvec4{jobs, m_candidates, m_entries, 1u}, groups_x, groups_y);
|
||||
// the output and the counts go to draws, indirect draws and the copy for the cpu
|
||||
compute_barrier(Commands);
|
||||
if (false == m_twophase)
|
||||
{
|
||||
read_counters(Commands);
|
||||
}
|
||||
dispatch_end();
|
||||
}
|
||||
|
||||
void instance_cull::dispatch_second(command_stream *Commands, cull_occlusion const &Occlusion)
|
||||
{
|
||||
if (!m_dispatched || !m_twophase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &shared{*m_shared};
|
||||
// the draws of the first phase read the output and the commands the second phase writes (other slots of them)
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT,
|
||||
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
|
||||
auto const groups_x{std::min<std::uint32_t>(groups, 65535)};
|
||||
auto const groups_y{(groups + groups_x - 1) / groups_x};
|
||||
shared.cull.dispatch(Commands, cull_descriptors(&Occlusion), glm::uvec4{static_cast<std::uint32_t>(m_joblist.size()), m_candidates, m_entries, 2u}, groups_x, groups_y);
|
||||
compute_barrier(Commands);
|
||||
if (m_commandcount > 0)
|
||||
{
|
||||
shared.commands.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_commands->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_commandcount, m_entries, 1u, 0u},
|
||||
(m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
|
||||
}
|
||||
// the commands go to indirect draws, the matrices to the vertex shaders, the counts to the copy for the cpu
|
||||
compute_barrier(Commands);
|
||||
read_counters(Commands);
|
||||
}
|
||||
|
||||
void instance_cull::fill_commands(command_stream *Commands, std::vector<gl::instance_draw_command> const &Commands_)
|
||||
{
|
||||
m_commandcount = static_cast<std::uint32_t>(Commands_.size());
|
||||
if (!m_dispatched || Commands_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &shared{*m_shared};
|
||||
auto const &environment{shared.environment};
|
||||
// the first copy is sent, the others are made from it on the gpu
|
||||
auto const size{static_cast<VkDeviceSize>(Commands_.size() * sizeof(gl::instance_draw_command))};
|
||||
if (false == reserve(m_commands, 3 * size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, "instance commands"))
|
||||
{
|
||||
m_commandcount = 0;
|
||||
return;
|
||||
}
|
||||
before_writes(Commands);
|
||||
auto const slice{environment.ring->allocate(size, 16)};
|
||||
if (slice)
|
||||
{
|
||||
std::memcpy(slice.data, Commands_.data(), static_cast<std::size_t>(size));
|
||||
VkBufferCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
|
||||
region.srcOffset = slice.offset;
|
||||
region.dstOffset = 0;
|
||||
region.size = size;
|
||||
VkCopyBufferInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
|
||||
copy.srcBuffer = slice.buffer;
|
||||
copy.dstBuffer = m_commands->handle();
|
||||
copy.regionCount = 1;
|
||||
copy.pRegions = ®ion;
|
||||
Commands->copy_buffer2(©);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the ring of the frame is full: the commands written in pieces with the commands of the frame
|
||||
auto const *data{reinterpret_cast<std::uint8_t const *>(Commands_.data())};
|
||||
for (VkDeviceSize offset = 0; offset < size; offset += 65536)
|
||||
{
|
||||
Commands->update_buffer(m_commands->handle(), offset, std::min<VkDeviceSize>(65536, size - offset), data + offset);
|
||||
}
|
||||
}
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
shared.commands.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_commands->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_commandcount, m_entries, 0u, 0u},
|
||||
(m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
|
||||
// the commands go to indirect draws, the matrices to the vertex shaders
|
||||
compute_barrier(Commands);
|
||||
}
|
||||
|
||||
// the depth pyramid
|
||||
|
||||
bool hiz_pyramid::init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout)
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_kernel.create(Device, Pipelines, Shaders.get("hiz_build.comp"), Passlayout, {}, "hi-z");
|
||||
}
|
||||
catch (gfx::shader_exception const &Exception)
|
||||
{
|
||||
log_error(Exception.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void hiz_pyramid::destroy(VkDevice Device, deletion_queue *Deletions, std::uint64_t const Value)
|
||||
{
|
||||
if (Deletions != nullptr)
|
||||
{
|
||||
// the frames in flight may still read them
|
||||
Deletions->push(Value, [Device, levels = std::move(m_levels), pyramid = std::move(m_pyramid)]() {
|
||||
for (auto *view : levels)
|
||||
{
|
||||
vkDestroyImageView(Device, view, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto *view : m_levels)
|
||||
{
|
||||
vkDestroyImageView(Device, view, nullptr);
|
||||
}
|
||||
m_kernel.destroy(Device);
|
||||
m_pyramid.reset();
|
||||
}
|
||||
m_levels.clear();
|
||||
m_pyramid.reset();
|
||||
m_sourcesize = glm::ivec2{0};
|
||||
m_size = glm::ivec2{0};
|
||||
}
|
||||
|
||||
bool hiz_pyramid::build(command_stream *Commands, VkDevice Device, allocator &Memory, deletion_queue &Deletions, std::uint64_t const Value, VkImageView const Depth, VkSampler const Sampler, int const Width, int const Height)
|
||||
{
|
||||
if ((Width <= 0) || (Height <= 0) || (false == bool(m_kernel)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((m_sourcesize != glm::ivec2{Width, Height}) || (false == bool(m_pyramid)))
|
||||
{
|
||||
if (m_pyramid)
|
||||
{
|
||||
destroy(Device, &Deletions, Value);
|
||||
}
|
||||
m_sourcesize = glm::ivec2{Width, Height};
|
||||
m_size = glm::ivec2{(Width + 1) / 2, (Height + 1) / 2};
|
||||
std::uint32_t levels{1};
|
||||
while ((std::max(m_size.x, m_size.y) >> levels) > 0)
|
||||
{
|
||||
++levels;
|
||||
}
|
||||
image_desc desc;
|
||||
desc.format = VK_FORMAT_R32_SFLOAT;
|
||||
desc.extent = {static_cast<std::uint32_t>(m_size.x), static_cast<std::uint32_t>(m_size.y), 1};
|
||||
desc.mips = levels;
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
m_pyramid = std::make_shared<image>();
|
||||
if (false == m_pyramid->create(Memory, desc, "hi-z pyramid"))
|
||||
{
|
||||
m_pyramid.reset();
|
||||
m_sourcesize = glm::ivec2{0};
|
||||
return false;
|
||||
}
|
||||
for (std::uint32_t level = 0; level < levels; ++level)
|
||||
{
|
||||
VkImageViewCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
info.image = m_pyramid->handle();
|
||||
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
info.format = desc.format;
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, level, 1, 0, 1};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view));
|
||||
m_levels.push_back(view);
|
||||
}
|
||||
// the pyramid stays in the general layout: the kernel reads the levels it wrote, the culling all of them
|
||||
transition(Commands, *m_pyramid, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the culling done with the pyramid made before
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
}
|
||||
auto source{Depth};
|
||||
auto sourcelayout{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
auto sourcesize{m_sourcesize};
|
||||
for (std::size_t level = 0; level < m_levels.size(); ++level)
|
||||
{
|
||||
auto const targetsize{glm::max(m_size >> static_cast<int>(level), glm::ivec2{1})};
|
||||
hiz_constants constants{glm::ivec4{sourcesize, targetsize}, glm::ivec4{0}};
|
||||
m_kernel.dispatch(Commands, {image_descriptor(Sampler, source, sourcelayout), storage_image(m_levels[level])}, constants, (targetsize.x + gl::HIZ_GROUP_SIZE - 1) / gl::HIZ_GROUP_SIZE,
|
||||
(targetsize.y + gl::HIZ_GROUP_SIZE - 1) / gl::HIZ_GROUP_SIZE);
|
||||
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
source = m_levels[level];
|
||||
sourcelayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
sourcesize = targetsize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
cull_occlusion hiz_pyramid::occlusion(VkSampler const Sampler, glm::mat4 const &Projection) const
|
||||
{
|
||||
cull_occlusion result;
|
||||
if (false == bool(m_pyramid))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
result.pyramid = m_pyramid->view();
|
||||
result.sampler = Sampler;
|
||||
result.size = m_size;
|
||||
result.levels = static_cast<int>(m_levels.size());
|
||||
result.screen = glm::vec2{m_sourcesize};
|
||||
result.projection = Projection;
|
||||
return result;
|
||||
}
|
||||
|
||||
// the cascades fitted to the depth of the view
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct fit_depth_constants
|
||||
{
|
||||
glm::mat4 inv_projection;
|
||||
};
|
||||
|
||||
struct fit_bounds_constants
|
||||
{
|
||||
glm::mat4 inv_projection;
|
||||
glm::mat4 view_to_light;
|
||||
glm::vec4 split_params;
|
||||
glm::vec4 margin_params;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool shadow_fitter::init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator const &Memory, std::uint32_t const Frames)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((false == m_depthkernel.create(Device, Pipelines, Shaders.get("shadow_fit_depth.comp"), Passlayout, {}, "shadow fit depth")) ||
|
||||
(false == m_boundskernel.create(Device, Pipelines, Shaders.get("shadow_fit_bounds.comp"), Passlayout, {}, "shadow fit bounds")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (gfx::shader_exception const &Exception)
|
||||
{
|
||||
log_error(Exception.what());
|
||||
return false;
|
||||
}
|
||||
return m_buffer.create(Memory, sizeof(gl::shadow_fit_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::device, "shadow fit") &&
|
||||
m_readback.create(Memory, sizeof(gl::shadow_fit_data), Frames, "shadow fit readback");
|
||||
}
|
||||
|
||||
void shadow_fitter::destroy(VkDevice Device)
|
||||
{
|
||||
m_depthkernel.destroy(Device);
|
||||
m_boundskernel.destroy(Device);
|
||||
m_buffer.destroy();
|
||||
m_readback.destroy();
|
||||
}
|
||||
|
||||
void shadow_fitter::measure(command_stream *Commands, VkImageView const Depth, VkSampler const Sampler, int const Width, int const Height, view const &View, unsigned int const Frame, std::uint32_t const Slot,
|
||||
std::uint64_t const Value)
|
||||
{
|
||||
if ((Width <= 0) || (Height <= 0) || (View.range <= 0.f) || (false == bool(*this)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const data{begin_measurement(View, Frame)};
|
||||
update_buffer(Commands, m_buffer, &data, sizeof(data), 0);
|
||||
auto const inverseprojection{glm::inverse(View.projection)};
|
||||
auto const viewtolight{glm::mat4{View.light * glm::inverse(View.rotation)}};
|
||||
auto const groupsx{(static_cast<std::uint32_t>(Width) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
|
||||
auto const groupsy{(static_cast<std::uint32_t>(Height) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
|
||||
std::vector<descriptor> const descriptors{image_descriptor(Sampler, Depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), buffer_descriptor(m_buffer.handle(), 0, VK_WHOLE_SIZE)};
|
||||
m_depthkernel.dispatch(Commands, descriptors, fit_depth_constants{inverseprojection}, groupsx, groupsy);
|
||||
compute_barrier(Commands);
|
||||
m_boundskernel.dispatch(Commands, descriptors, fit_bounds_constants{inverseprojection, viewtolight, bounds_params(View), margin_params()}, groupsx, groupsy);
|
||||
compute_barrier(Commands);
|
||||
m_readback.push(Commands, Slot, Value, m_buffer.handle(), 0, sizeof(gl::shadow_fit_data));
|
||||
}
|
||||
|
||||
shadow_fitter::result const *shadow_fitter::latest(std::uint64_t const Completed)
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
if (m_readback.pop(Completed, &data, sizeof(data)))
|
||||
{
|
||||
arrived(data);
|
||||
}
|
||||
return gfx::shadow_fit::latest();
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
196
rendering/vulkan14/vulkan14cull.h
Normal file
196
rendering/vulkan14/vulkan14cull.h
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
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 gpu culling of the instances in the vulkan renderer (the stage P5 of the plan): the buffers and the compute passes of
|
||||
// gfx::instance_cull, the opengl46_instance_cull of this renderer, with the same shaders (instance_cull.comp,
|
||||
// instance_cull_offsets.comp, instance_cull_commands.comp), and the depth pyramid of its occlusion test (hiz_build.comp, the
|
||||
// opengl46_hiz of this renderer). what differs from the opengl renderer:
|
||||
// - the records go to the gpu with the uploads of the frame, which come before its commands; a record buffer started over gets
|
||||
// a new buffer, so the passes of the frame culled before keep the places they had
|
||||
// - the jobs, the groups, the lod bounds and the parameters of a pass are read by the kernels from the ring of the frame
|
||||
// - the counters are copied for the cpu at the end of the last stage of the culling, in the frame being recorded (the rendering
|
||||
// of the pass is suspended for the dispatches)
|
||||
// - the output is the instance buffer of the indirect draws in pass sets of its own (vulkan14_renderer::cull_pass_sets())
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/instancecull.h"
|
||||
#include "rendering/common/shadowfit.h"
|
||||
#include "rendering/vulkan14/vulkan14compute.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// what the cullings need of the renderer
|
||||
struct cull_environment
|
||||
{
|
||||
allocator *memory{nullptr};
|
||||
uploader *uploads{nullptr};
|
||||
deletion_queue *deletions{nullptr};
|
||||
frame_ring *ring{nullptr}; // the ring of the frame, the data of the passes go through it
|
||||
VkDeviceSize uniform_alignment{256};
|
||||
VkDeviceSize storage_alignment{256};
|
||||
std::uint32_t frames{2}; // in flight
|
||||
// the frame being recorded: its slot among the frames in flight, the value of the timeline the gpu reaches when it's done with it,
|
||||
// and the value the gpu reached
|
||||
std::uint32_t slot{0};
|
||||
std::uint64_t value{0};
|
||||
std::uint64_t completed{0};
|
||||
// a sampled image to give the occlusion test of the kernel when there's no pyramid
|
||||
descriptor no_pyramid{};
|
||||
};
|
||||
|
||||
// the occlusion test of a pass: the pyramid (the view of all its levels, in the general layout) and what it was made from
|
||||
struct cull_occlusion
|
||||
{
|
||||
VkImageView pyramid{VK_NULL_HANDLE};
|
||||
VkSampler sampler{VK_NULL_HANDLE};
|
||||
glm::ivec2 size{0}; // of its first level
|
||||
int levels{0};
|
||||
glm::vec2 screen{0.f}; // size of the depth it was made from, pixels
|
||||
glm::mat4 projection{1.f}; // of the pass
|
||||
};
|
||||
|
||||
class instance_cull : public gfx::instance_cull
|
||||
{
|
||||
public:
|
||||
instance_cull() = default;
|
||||
~instance_cull() override;
|
||||
|
||||
// the kernels and the record buffer made, or taken from Shared. Shaders: the shader library
|
||||
bool init(cull_environment const &Environment, VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, instance_cull const *Shared = nullptr);
|
||||
void destroy(VkDevice Device);
|
||||
explicit operator bool() const
|
||||
{
|
||||
return bool(m_shared);
|
||||
}
|
||||
// the frame being recorded (cull_environment::slot, value, completed)
|
||||
void begin_frame(std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Completed);
|
||||
// as opengl46_instance_cull::dispatch(), dispatch_second(), fill_commands(), outside a rendering
|
||||
void dispatch(command_stream *Commands, glm::mat4 const &View, cull_occlusion const *Occlusion = nullptr, bool const Twophase = false);
|
||||
void dispatch_second(command_stream *Commands, cull_occlusion const &Occlusion);
|
||||
void fill_commands(command_stream *Commands, std::vector<gl::instance_draw_command> const &Commands_);
|
||||
// the indirect commands (draw_command_offset()) and the matrices of the instances drawn
|
||||
VkBuffer command_buffer() const
|
||||
{
|
||||
return m_commands ? m_commands->handle() : VK_NULL_HANDLE;
|
||||
}
|
||||
VkBuffer output_buffer() const
|
||||
{
|
||||
return m_output ? m_output->handle() : VK_NULL_HANDLE;
|
||||
}
|
||||
VkDeviceSize output_size() const
|
||||
{
|
||||
return m_output ? m_output->size() : 0;
|
||||
}
|
||||
// the pass sets of the renderer with the output as the instance buffer, made for the generation of the pass sets and the output
|
||||
std::array<VkDescriptorSet, 3> sets{};
|
||||
VkDescriptorPool set_pool{VK_NULL_HANDLE};
|
||||
std::uint64_t sets_generation{0};
|
||||
VkBuffer sets_output{VK_NULL_HANDLE};
|
||||
// the gpu time of the passes, for the panel (the parts of the pass list)
|
||||
std::string info_times(std::string const &Label = "instance cull") const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
std::int64_t record_buffer_size() const override;
|
||||
void resize_record_buffer(std::int64_t const Size) override;
|
||||
void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) override;
|
||||
void restart_records() override;
|
||||
void push_counts() override;
|
||||
bool pop_counts(std::uint32_t *Counts) override;
|
||||
|
||||
private:
|
||||
// what the cullings of all passes share: the kernels, the record buffer and the records sent since the last dispatch
|
||||
struct shared_data
|
||||
{
|
||||
compute_kernel cull;
|
||||
compute_kernel offsets;
|
||||
compute_kernel commands;
|
||||
std::shared_ptr<buffer> records;
|
||||
std::vector<std::uint8_t> pending;
|
||||
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> parts;
|
||||
cull_environment environment;
|
||||
};
|
||||
// a buffer of the culling grown to hold at least Size bytes, at least doubled; the old one goes once the gpu is done with it
|
||||
bool reserve(std::shared_ptr<buffer> &Buffer, VkDeviceSize Size, VkBufferUsageFlags Usage, char const *Name);
|
||||
// the records sent since the last dispatch go with the uploads of the frame
|
||||
void flush_records();
|
||||
// the descriptors of the culling kernel; Occlusion: the pyramid, or none
|
||||
std::vector<descriptor> cull_descriptors(cull_occlusion const *Occlusion) const;
|
||||
// the counters copied for the cpu
|
||||
void read_counters(command_stream *Commands);
|
||||
|
||||
std::shared_ptr<shared_data> m_shared;
|
||||
std::shared_ptr<buffer> m_counters;
|
||||
std::shared_ptr<buffer> m_output;
|
||||
std::shared_ptr<buffer> m_commands;
|
||||
readback_ring m_readback;
|
||||
// the slices of the ring the jobs, the groups, the lod bounds and the parameters of the pass were placed in
|
||||
ring_slice m_jobs;
|
||||
ring_slice m_groups;
|
||||
ring_slice m_lods;
|
||||
ring_slice m_block;
|
||||
VkDeviceSize m_countersize{0};
|
||||
};
|
||||
|
||||
// the depth pyramid of the occlusion test of the main view (opengl46_hiz): made from the resolved depth of the pass, the first
|
||||
// level half its size, each level the farthest depth of the 2x2 texels below it
|
||||
class hiz_pyramid
|
||||
{
|
||||
public:
|
||||
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout);
|
||||
void destroy(VkDevice Device, deletion_queue *Deletions = nullptr, std::uint64_t Value = 0);
|
||||
// the pyramid made from Depth (a sampled image of Width x Height, in the read only layout), the images made anew for another size
|
||||
bool build(command_stream *Commands, VkDevice Device, allocator &Memory, deletion_queue &Deletions, std::uint64_t Value, VkImageView Depth, VkSampler Sampler, int Width, int Height);
|
||||
// the occlusion test of the pass
|
||||
cull_occlusion occlusion(VkSampler Sampler, glm::mat4 const &Projection) const;
|
||||
|
||||
private:
|
||||
compute_kernel m_kernel;
|
||||
std::shared_ptr<image> m_pyramid;
|
||||
std::vector<VkImageView> m_levels;
|
||||
glm::ivec2 m_sourcesize{0};
|
||||
glm::ivec2 m_size{0};
|
||||
};
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm, opengl46_shadow_fit): the passes and the
|
||||
// buffers of gfx::shadow_fit, the result read back through a ring
|
||||
class shadow_fitter : public gfx::shadow_fit
|
||||
{
|
||||
public:
|
||||
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator const &Memory, std::uint32_t Frames);
|
||||
void destroy(VkDevice Device);
|
||||
explicit operator bool() const
|
||||
{
|
||||
return bool(m_depthkernel) && bool(m_boundskernel) && bool(m_buffer);
|
||||
}
|
||||
// measures the depth of the view: Depth, the resolved depth of Width x Height pixels in the read only layout, outside a rendering.
|
||||
// Frame: the frame of the renderer; Slot, Value: the frame being recorded, as the readback rings take it
|
||||
void measure(command_stream *Commands, VkImageView Depth, VkSampler Sampler, int Width, int Height, view const &View, unsigned int Frame, std::uint32_t Slot, std::uint64_t Value);
|
||||
// takes in the newest measurement the gpu has finished (Completed: the value of the timeline it reached), and returns the
|
||||
// newest one taken in so far
|
||||
result const *latest(std::uint64_t Completed);
|
||||
|
||||
private:
|
||||
compute_kernel m_depthkernel;
|
||||
compute_kernel m_boundskernel;
|
||||
buffer m_buffer;
|
||||
readback_ring m_readback;
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
440
rendering/vulkan14/vulkan14debug.cpp
Normal file
440
rendering/vulkan14/vulkan14debug.cpp
Normal file
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
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 tools of the vulkan renderer (the stage P7 of the plan): the debug windows of opengl46_renderer::draw_debug_ui() over the
|
||||
// settings and the state shared with the opengl renderer, the lines of the statistics of the scene and of the cullings for
|
||||
// the debug panel (Shift+F12), and the heatmap of the light clusters (gfx.lights.clusterheatmap)
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14renderer.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
|
||||
#include "rendering/common/exposure.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
VkPipelineStageFlags2 constexpr heatmap_colour_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||
VkAccessFlags2 constexpr heatmap_colour_access{VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT};
|
||||
|
||||
} // namespace
|
||||
|
||||
void vulkan14_renderer::collect_cluster_stats()
|
||||
{
|
||||
if (!m_context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &clusters{m_context->scene.clusters};
|
||||
gl::cluster_counters counters{};
|
||||
if (false == clusters.counters_readback.pop(m_context->completed(), &counters, sizeof(counters)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &stats{clusters.stats};
|
||||
stats.index_count = counters.index_count;
|
||||
stats.overflow_count = counters.overflow_count;
|
||||
stats.overflow_peak = std::max(counters.overflow_count, stats.valid ? stats.overflow_peak : 0u);
|
||||
stats.valid = true;
|
||||
}
|
||||
|
||||
// the lines of the cullings of the passes (the part of opengl46_renderer::Update_Debug_Info() after the scene statistics)
|
||||
std::string vulkan14_renderer::cull_info()
|
||||
{
|
||||
std::string text;
|
||||
if (!m_context || (false == m_context->scene.cull.ready))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
auto &cull{m_context->scene.cull};
|
||||
text += cull.main.info();
|
||||
if (Global.gfx_gpucull_shadows)
|
||||
{
|
||||
for (std::size_t cascade = 0; cascade < cull.cascades.size(); ++cascade)
|
||||
{
|
||||
if (false == cull.cascades[cascade].ran())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
cull.cascades[cascade].collect();
|
||||
text += cull.cascades[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false);
|
||||
}
|
||||
}
|
||||
if (Global.gfx_gpucull_reflections && cull.reflections.ran())
|
||||
{
|
||||
cull.reflections.collect();
|
||||
text += cull.reflections.info("gpu cull reflections, last face", false);
|
||||
}
|
||||
if (Global.gfx_gpucull != 0)
|
||||
{
|
||||
// the maps are drawn now and then, the numbers are of the last drawing of each
|
||||
auto const add = [&](std::vector<std::unique_ptr<vk14::instance_cull>> &Culls, bool const Enabled, std::string const &Label) {
|
||||
if (false == Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (std::size_t idx = 0; idx < Culls.size(); ++idx)
|
||||
{
|
||||
if (!Culls[idx] || (false == Culls[idx]->ran()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Culls[idx]->collect();
|
||||
text += Culls[idx]->info("gpu cull " + Label + " " + std::to_string(idx + 1), false);
|
||||
}
|
||||
};
|
||||
add(cull.headlights, Global.gfx_gpucull_shadows_headlights, "headlight shadows");
|
||||
add(cull.lamps, Global.gfx_gpucull_shadows_lamps, "lamp shadows, scenery");
|
||||
}
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
// all passes of the last frame
|
||||
auto const &walks{m_lastwalkcounts};
|
||||
text += "\nwalks of the submodel trees in the frame: " + std::to_string(walks.walked) + " made (" + std::to_string(walks.changing) + " not kept), " + std::to_string(walks.reused) +
|
||||
" kept from earlier frames (" + std::to_string(m_walkrecords.size()) + " groups), cpu " + to_string(walks.walktime, 3) + " ms; the draws put in order and sent, cpu " +
|
||||
to_string(walks.ordertime, 3) + " ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// the statistics of the scene (the part "Scene statistics" of opengl46_renderer::Update_Debug_Info())
|
||||
std::string vulkan14_renderer::scene_info()
|
||||
{
|
||||
auto &scene{m_context->scene};
|
||||
debug_stats shadowstats;
|
||||
for (auto const &shadowpass : m_shadowpass)
|
||||
{
|
||||
shadowstats += shadowpass.draw_stats;
|
||||
}
|
||||
auto const &colour{m_laststats};
|
||||
auto const line = [](char const *Name, int const Colour, int const Shadows) {
|
||||
return std::string(Name) + to_string(Colour, 7) + " +" + to_string(Shadows, 7) + " =" + to_string(Colour + Shadows, 7) + "\n";
|
||||
};
|
||||
// the cascades of the sun shadows: where they end and the size of a texel of their maps, fixed or fitted to the depth
|
||||
std::string cascades;
|
||||
if (Global.gfx_shadowmap_enabled && (scene.shadow_size > 0))
|
||||
{
|
||||
auto const &fit{m_shadowfitstate};
|
||||
auto const size{static_cast<float>(scene.shadow_size)};
|
||||
cascades = " sun shadow cascades to";
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
cascades += " " + to_string(fit.splits[cascade + 1], 1);
|
||||
}
|
||||
cascades += " m, texels";
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
cascades += " " + to_string(100.f * fit.drawn[cascade] / size, 1);
|
||||
}
|
||||
cascades += " cm";
|
||||
if (Global.gfx_shadow_sdsm)
|
||||
{
|
||||
if (fit.fitted)
|
||||
{
|
||||
cascades += " (whole slices";
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
cascades += " " + to_string(100.f * fit.slice[cascade] / size, 1);
|
||||
}
|
||||
cascades += "), fitted to pixels " + to_string(fit.depthmin, 2) + "-" + to_string(fit.depthmax, 1) + " m " + std::to_string(fit.age) + " frames ago, view turned " + to_string(fit.turn, 1) +
|
||||
" deg since";
|
||||
}
|
||||
else
|
||||
{
|
||||
cascades += ", fixed: " + std::string{fit.reason != nullptr ? fit.reason : ""};
|
||||
}
|
||||
}
|
||||
cascades += "\n";
|
||||
}
|
||||
std::string text{"triangles: " + to_string(static_cast<int>(m_geometry.primitives_count()), 7) + "\n" + line("vehicles: ", colour.dynamics, shadowstats.dynamics) +
|
||||
line("models: ", colour.models, shadowstats.models) + line("drawcalls: ", colour.drawcalls, shadowstats.drawcalls) + " instanced:" + to_string(colour.instances, 7) + " +" +
|
||||
to_string(shadowstats.instances, 7) + " =" + to_string(colour.instances + shadowstats.instances, 7) + " (" +
|
||||
std::to_string(colour.instanced_drawcalls + shadowstats.instanced_drawcalls) + " batches)\n"};
|
||||
if (Global.gfx_contactshadows > 0)
|
||||
{
|
||||
text += " contact shadows: " + std::string{Global.gfx_contactshadows == 3 ? "high" : Global.gfx_contactshadows == 2 ? "medium" : "low"} + ", rays " +
|
||||
to_string(Global.gfx_contactshadows_length, 2) + " m within " + to_string(Global.gfx_contactshadows_range, 0) + " m\n";
|
||||
}
|
||||
text += cascades + line(" submodels:", colour.submodels, shadowstats.submodels) + line(" paths: ", colour.paths, shadowstats.paths) +
|
||||
line(" shapes: ", colour.shapes, shadowstats.shapes) + " traction: " + to_string(colour.traction, 7) + "\n" + " lines: " + to_string(colour.lines, 7) + "\n" +
|
||||
"particles: " + to_string(colour.particles, 7) + "\nlights: " + to_string(static_cast<int>(m_lights_lit), 7) + " (" + std::to_string(m_lights_dropped) + " over capacity), " +
|
||||
(Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string(std::max(Global.DynamicLightCount, 0))) + "\n" + m_locallights.info() + scene.lamps.info() +
|
||||
scene.particles.info();
|
||||
auto const &stats{scene.clusters.stats};
|
||||
if (stats.valid && (Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap))
|
||||
{
|
||||
text += "cluster lists: " + std::to_string(stats.index_count) + ", overflow " + std::to_string(stats.overflow_count) + " (peak " + std::to_string(stats.overflow_peak) + ")\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::draw_debug_ui()
|
||||
{
|
||||
if ((false == m_debug_ui_active) || !m_context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &scene{m_context->scene};
|
||||
collect_cluster_stats();
|
||||
|
||||
if (ImGui::Begin("Headlight config", &m_debug_ui_active))
|
||||
{
|
||||
ImGui::SetWindowSize(ImVec2(0, 0));
|
||||
auto &conf{m_headlightconfig};
|
||||
ImGui::SliderFloat("in_cutoff", &conf.in_cutoff, 0.9f, 1.1f);
|
||||
ImGui::SliderFloat("out_cutoff", &conf.out_cutoff, 0.9f, 1.1f);
|
||||
ImGui::SliderFloat("falloff_linear", &conf.falloff_linear, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("falloff_quadratic", &conf.falloff_quadratic, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("ambient", &conf.ambient, 0.0f, 3.0f);
|
||||
ImGui::SliderFloat("intensity", &conf.intensity, 0.0f, 10.0f);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Light clusters"))
|
||||
{
|
||||
ImGui::Checkbox("clustered lights (off: the first dynamiclights headlights)", &Global.gfx_lights_clustered);
|
||||
ImGui::Checkbox("heatmap", &Global.gfx_lights_clusterheatmap);
|
||||
ImGui::Checkbox("beams of vehicle headlights (gfx.lights.headlights)", &Global.gfx_lights_headlights);
|
||||
auto const ¶ms{scene.clusters.params};
|
||||
ImGui::Text("grid %u x %u x %u, %s", gl::CLUSTER_GRID_X, gl::CLUSTER_GRID_Y, gl::CLUSTER_GRID_Z, params.valid ? "built" : "none (orthographic view)");
|
||||
ImGui::Text("near %.3f m, far %.1f m", params.znear, params.zfar);
|
||||
ImGui::Text("headlights %u, %u over capacity of %u", m_lights_lit, m_lights_dropped, static_cast<unsigned>(gl::MAX_LIGHTS - 1));
|
||||
auto const &stats{scene.clusters.stats};
|
||||
if (stats.valid)
|
||||
{
|
||||
ImGui::Text("index list %u of %u", stats.index_count, gl::CLUSTER_COUNT * gl::MAX_LIGHTS_PER_CLUSTER);
|
||||
ImGui::Text("overflow: %u lights dropped from full clusters (limit %u each), peak %u", stats.overflow_count, gl::MAX_LIGHTS_PER_CLUSTER, stats.overflow_peak);
|
||||
}
|
||||
ImGui::Separator();
|
||||
m_locallights.draw_debug_ui();
|
||||
ImGui::Separator();
|
||||
scene.lamps.draw_debug_ui();
|
||||
ImGui::Separator();
|
||||
char const *contactlevels[]{"no", "low (the sun)", "medium (the sun and 2 lamps)", "high (the sun and 4 lamps)"};
|
||||
ImGui::Combo("contact shadows (gfx.contactshadows)", &Global.gfx_contactshadows, contactlevels, IM_ARRAYSIZE(contactlevels));
|
||||
ImGui::SliderFloat("length of their rays, m (gfx.contactshadows.length)", &Global.gfx_contactshadows_length, 0.05f, 5.f, "%.2f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("distance they reach, m (gfx.contactshadows.range)", &Global.gfx_contactshadows_range, 1.f, 1000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sun shadows"))
|
||||
{
|
||||
ImGui::Checkbox("cascades fitted to the depth of the view (gfx.shadow.sdsm)", &Global.gfx_shadow_sdsm);
|
||||
ImGui::SliderFloat("splits, 0 equal lengths .. 1 equal ratios (gfx.shadow.sdsm.split)", &Global.gfx_shadow_sdsm_split, 0.f, 1.f, "%.2f");
|
||||
auto const &fit{m_shadowfitstate};
|
||||
if (Global.gfx_shadow_sdsm)
|
||||
{
|
||||
if (fit.fitted)
|
||||
{
|
||||
ImGui::Text("fitted to pixels %.2f - %.1f m, measured %u frames ago, view turned %.1f deg since", fit.depthmin, fit.depthmax, fit.age, fit.turn);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Text("fixed cascades: %s", fit.reason != nullptr ? fit.reason : "");
|
||||
}
|
||||
}
|
||||
if (scene.shadow_size > 0)
|
||||
{
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
ImGui::Text("cascade %d: to %.1f m, map %.1f m across (whole slice %.1f m), texel %.1f cm", static_cast<int>(cascade + 1), fit.splits[cascade + 1], fit.drawn[cascade], fit.slice[cascade],
|
||||
100.f * fit.drawn[cascade] / static_cast<float>(scene.shadow_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Weather"))
|
||||
{
|
||||
m_weatherstate.draw_debug_ui();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Particles"))
|
||||
{
|
||||
ImGui::Checkbox("smoke simulated and drawn on the gpu (gfx.smoke.gpu)", &Global.gfx_smoke_gpu);
|
||||
if (Global.gfx_smoke_gpu)
|
||||
{
|
||||
ImGui::Checkbox("smoke lit by the sun, the sky and the lamps (gfx.smoke.lighting)", &Global.gfx_smoke_lighting);
|
||||
}
|
||||
ImGui::Checkbox("rain and snow as particles of the gpu (gfx.precipitation.gpu)", &Global.gfx_precipitation_gpu);
|
||||
if (Global.gfx_precipitation_gpu)
|
||||
{
|
||||
ImGui::SliderInt("particles of the heaviest rain or snow (gfx.precipitation.particles)", &Global.gfx_precipitation_particles, 0, 262144);
|
||||
}
|
||||
ImGui::SliderFloat("soft particles, share of their size, 0: off (gfx.particles.soft)", &Global.gfx_particles_soft, 0.f, 4.f, "%.2f");
|
||||
auto const info{scene.particles.info()};
|
||||
if (false == info.empty())
|
||||
{
|
||||
ImGui::TextUnformatted(info.c_str() + 1);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sky and exposure"))
|
||||
{
|
||||
m_skylight.draw_debug_ui();
|
||||
ImGui::SliderFloat("sun disc, 1 = physical (gfx.sun.disc)", &Global.gfx_sun_disc, 0.f, 2.f);
|
||||
ImGui::SliderFloat("moon disc (gfx.moon.disc)", &Global.gfx_moon_disc, 0.f, 8.f);
|
||||
ImGui::SliderFloat("moonlight on the scenery (gfx.moonlight)", &Global.gfx_moonlight, 0.f, 10.f);
|
||||
{
|
||||
// where the sun and the moon are: azimuth from the north to the east (the sky has x to the west, z to the north)
|
||||
auto const azimuth = [](glm::vec3 const &Direction) { return glm::degrees(std::atan2(-Direction.x, Direction.z)) + (Direction.x > 0.f ? 360.f : 0.f); };
|
||||
auto const &sun{simulation::Environment.sun()};
|
||||
auto const &moon{simulation::Environment.moon()};
|
||||
ImGui::Text("sun: elevation %.1f, azimuth %.1f", sun.getAngle(), azimuth(sun.getDirection()));
|
||||
ImGui::Text("moon: elevation %.1f, azimuth %.1f, lit %.0f%%, age %.1f d", moon.getAngle(), azimuth(moon.getDirection()), 100.f * moon.getIllumination(), moon.getPhase());
|
||||
}
|
||||
ImGui::Separator();
|
||||
gfx::exposure_settings::draw_debug_ui(scene.exposure.values);
|
||||
ImGui::Separator();
|
||||
// the ambient occlusion (opengl46_ao::draw_debug_ui()): its images are made with the targets of the viewports
|
||||
if (scene.targets.empty() || (scene.targets.front().ssao == nullptr))
|
||||
{
|
||||
ImGui::TextUnformatted("ambient occlusion: off at the start, set gfx.postfx.ao in eu07.ini");
|
||||
}
|
||||
else
|
||||
{
|
||||
char const *modes[]{"off", "older ssao", "ground truth (gtao)"};
|
||||
ImGui::Combo("ambient occlusion (gfx.postfx.ao)", &Global.gfx_postfx_ao, modes, IM_ARRAYSIZE(modes));
|
||||
if (Global.gfx_postfx_ao == 2)
|
||||
{
|
||||
ImGui::SliderFloat("ao radius (m)", &Global.gfx_postfx_ao_radius, 0.1f, 10.f, "%.2f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("ao strength", &Global.gfx_postfx_ao_strength, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ao bounce albedo", &Global.gfx_postfx_ao_bounce, 0.f, 1.f);
|
||||
ImGui::SliderInt("ao slices", &Global.gfx_postfx_ao_slices, 1, 8);
|
||||
ImGui::SliderInt("ao steps per side", &Global.gfx_postfx_ao_steps, 1, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Instance culling"))
|
||||
{
|
||||
char const *modes[]{"cpu", "gpu, drawn from its result", "cpu, compared with the gpu"};
|
||||
ImGui::Combo("instance culling (gfx.gpucull)", &Global.gfx_gpucull, modes, IM_ARRAYSIZE(modes));
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
ImGui::Checkbox("leave out instances hidden behind the depth drawn before them (gfx.gpucull.occlusion)", &Global.gfx_gpucull_occlusion);
|
||||
if (Global.gfx_gpucull_occlusion)
|
||||
{
|
||||
ImGui::Checkbox("draw the instances seen in the last frame first, to hide the others (gfx.gpucull.occlusion.instances)", &Global.gfx_gpucull_occlusion_instances);
|
||||
}
|
||||
ImGui::Checkbox("draw the depth of the instances before their colour (gfx.depthprepass)", &Global.gfx_depthprepass);
|
||||
ImGui::Checkbox("draw the instances of the same material and model with one multi draw (gfx.gpucull.multidraw)", &Global.gfx_gpucull_multidraw);
|
||||
}
|
||||
if (Global.gfx_gpucull != 0)
|
||||
{
|
||||
ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows);
|
||||
ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections);
|
||||
ImGui::Checkbox("the same for the shadow maps of headlights (gfx.gpucull.shadows.headlights)", &Global.gfx_gpucull_shadows_headlights);
|
||||
ImGui::Checkbox("the same for the scenery of the shadow maps of lamps, when it's drawn next (gfx.gpucull.shadows.lamps)", &Global.gfx_gpucull_shadows_lamps);
|
||||
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
|
||||
ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models);
|
||||
ImGui::Checkbox("leave out the models of a cell lying wholly out of view, beyond their range or too small before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange);
|
||||
ImGui::Checkbox("keep the walks of the submodel trees of the instances from frame to frame (gfx.gpucull.walkcache)", &Global.gfx_gpucull_walkcache);
|
||||
}
|
||||
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
|
||||
if (Global.gfx_contactshadows > 0)
|
||||
{
|
||||
ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)");
|
||||
}
|
||||
ImGui::Checkbox("grass of the heightmap terrain (gfx.grass)", &Global.gfx_grass);
|
||||
if (Global.gfx_grass)
|
||||
{
|
||||
ImGui::SliderFloat("the grass drawn whole up to, m (gfx.grass.distance)", &Global.gfx_grass_distance, 10.f, 300.f, "%.0f");
|
||||
ImGui::SliderFloat("thinning out up to, m (gfx.grass.fadedistance)", &Global.gfx_grass_fadedistance, 10.f, 350.f, "%.0f");
|
||||
ImGui::SliderFloat("density of the grass (gfx.grass.density)", &Global.gfx_grass_density, 0.1f, 2.f, "%.2f");
|
||||
}
|
||||
auto const info{cull_info()};
|
||||
if (false == info.empty())
|
||||
{
|
||||
ImGui::TextUnformatted(info.c_str() + 1);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// the number of the lights of the cluster of each pixel tinted over the output of a viewport, from the depth of its scene
|
||||
// (opengl46_renderer::Render_cluster_heatmap()), after its post-processing. outside a rendering
|
||||
void vulkan14_renderer::render_cluster_heatmap(std::size_t const Index)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &targets{scene.targets[Index]};
|
||||
auto &draw{*m_draw};
|
||||
auto const commands{ctx.commands};
|
||||
if ((scene.clusters.params.valid == 0) || (false == bool(targets.output)) || (false == bool(targets.depth)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ((scene.cluster_heatmap == nullptr) && (false == scene.cluster_heatmap_tried))
|
||||
{
|
||||
scene.cluster_heatmap_tried = true;
|
||||
try
|
||||
{
|
||||
auto program{std::make_shared<vk14::scene_program>()};
|
||||
program->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
|
||||
program->fragment = ctx.shaders.get("cluster_heatmap.frag");
|
||||
program->fragment_name = "cluster_heatmap.frag";
|
||||
program->vertices = vk14::vertex_kind::none;
|
||||
program->name = "quad.vert + cluster_heatmap.frag";
|
||||
scene.cluster_heatmap = program;
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("light clusters: can't use the heatmap: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
if (scene.cluster_heatmap == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the depth of the scene as the colour pass resolved it
|
||||
vk14::barriers(commands, {vk14::use_of(*targets.output, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, heatmap_colour_stage, heatmap_colour_access),
|
||||
vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
|
||||
VkRenderingAttachmentInfo attachment{};
|
||||
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachment.imageView = targets.output->view();
|
||||
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
VkRenderingInfo rendering{};
|
||||
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
rendering.renderArea = {{0, 0}, targets.extent};
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
commands->begin_rendering(&rendering);
|
||||
// the clusters of the pass set of the colour pass, with the offsets it was drawn with
|
||||
auto const offsets{std::array<std::uint32_t, 4>{draw.scene_offset, draw.light_offset, draw.cluster_offset, draw.weather_offset}};
|
||||
start_draws(targets.output_formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), m_viewports[Index].main ? vk14::scene_state::set_colour : vk14::scene_state::set_plain, false);
|
||||
draw.scene_offset = offsets[0];
|
||||
draw.light_offset = offsets[1];
|
||||
draw.cluster_offset = static_cast<std::uint32_t>(scene.clusters.params_offset);
|
||||
draw.weather_offset = offsets[3];
|
||||
if (bind_scene_pipeline(*scene.cluster_heatmap, targets.output_formats, vk14::blend_mode::scene_alpha, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
|
||||
{
|
||||
bind_pass_set();
|
||||
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
|
||||
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_nearest, targets.depth->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
|
||||
scene.material_set.push(commands, scene.scene_layout, images.data());
|
||||
vk14::draw_constants constants;
|
||||
push_constants(&constants);
|
||||
commands->draw(4, 1, 0, 0);
|
||||
}
|
||||
commands->end_rendering();
|
||||
end_draws();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
namespace vk14
|
||||
@@ -20,14 +21,17 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
|
||||
{
|
||||
auto const handle{Device.handle()};
|
||||
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
|
||||
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name) {
|
||||
VkFormatProperties depthformat{};
|
||||
vkGetPhysicalDeviceFormatProperties(Device.physical(), VK_FORMAT_D32_SFLOAT, &depthformat);
|
||||
auto const depthlinear{(depthformat.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0};
|
||||
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name, VkSamplerAddressMode AddressV = VK_SAMPLER_ADDRESS_MODE_MAX_ENUM) {
|
||||
VkSamplerCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
info.magFilter = Filter;
|
||||
info.minFilter = Filter;
|
||||
info.mipmapMode = (Filter == VK_FILTER_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
|
||||
info.addressModeU = Address;
|
||||
info.addressModeV = Address;
|
||||
info.addressModeV = (AddressV != VK_SAMPLER_ADDRESS_MODE_MAX_ENUM ? AddressV : Address);
|
||||
info.addressModeW = Address;
|
||||
info.maxLod = (Mipmaps ? VK_LOD_CLAMP_NONE : 0.f);
|
||||
if (Aniso > 1.f)
|
||||
@@ -35,6 +39,8 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
|
||||
info.anisotropyEnable = VK_TRUE;
|
||||
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
|
||||
}
|
||||
// the border of the clamped samplers: the farthest depth of the reverse z, black
|
||||
info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
if (Compare)
|
||||
{
|
||||
// as GL_LEQUAL of the opengl renderer: 1 (in the shadow) where the reference isn't nearer the light than the map (reverse
|
||||
@@ -54,12 +60,16 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
|
||||
};
|
||||
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
|
||||
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
|
||||
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow");
|
||||
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow") &&
|
||||
make(repeat_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, 1.f, false, "repeat linear") &&
|
||||
make(panorama, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, false, 1.f, false, "panorama", VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
|
||||
make(repeat_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT, false, 1.f, false, "repeat nearest") &&
|
||||
make(depth, (depthlinear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST), VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, false, "depth");
|
||||
}
|
||||
|
||||
void samplers::destroy(VkDevice Device)
|
||||
{
|
||||
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow})
|
||||
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow, &repeat_linear, &panorama, &repeat_nearest, &depth})
|
||||
{
|
||||
if (*sampler != VK_NULL_HANDLE)
|
||||
{
|
||||
@@ -138,7 +148,7 @@ void push_set::destroy(VkDevice Device)
|
||||
}
|
||||
}
|
||||
|
||||
void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const
|
||||
void push_set::push(command_stream *Commands, VkPipelineLayout Layout, void const *Data) const
|
||||
{
|
||||
VkPushDescriptorSetWithTemplateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PUSH_DESCRIPTOR_SET_WITH_TEMPLATE_INFO;
|
||||
@@ -146,7 +156,7 @@ void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void cons
|
||||
info.layout = Layout;
|
||||
info.set = set;
|
||||
info.pData = Data;
|
||||
vkCmdPushDescriptorSetWithTemplate2(Commands, &info);
|
||||
Commands->push_descriptor_set_with_template2(&info, m_bindings.size() * m_stride, bind_point);
|
||||
}
|
||||
|
||||
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name)
|
||||
|
||||
@@ -28,6 +28,12 @@ struct samplers
|
||||
VkSampler clamp_linear{VK_NULL_HANDLE}; // images of the user interface, post-processing
|
||||
VkSampler clamp_nearest{VK_NULL_HANDLE};
|
||||
VkSampler shadow{VK_NULL_HANDLE}; // depth compared (shadow maps, reverse z)
|
||||
VkSampler repeat_linear{VK_NULL_HANDLE}; // repeated, mipmapped, without anisotropy (the noises of the clouds)
|
||||
VkSampler panorama{VK_NULL_HANDLE}; // repeated across, clamped up and down (the panorama of the clouds around the camera)
|
||||
VkSampler repeat_nearest{VK_NULL_HANDLE}; // repeated, texel by texel (the noise of the ssao)
|
||||
// the depth of the scene read by the post-processing, as the render targets of the depth of the opengl renderer: filtered where
|
||||
// the device filters the depth format, the border the farthest depth (0)
|
||||
VkSampler depth{VK_NULL_HANDLE};
|
||||
|
||||
bool create(device const &Device, float Anisotropy, std::string &Reason);
|
||||
void destroy(VkDevice Device);
|
||||
@@ -48,7 +54,7 @@ struct push_set
|
||||
bool create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name);
|
||||
void destroy(VkDevice Device);
|
||||
// pushes Data (laid out as create() was told)
|
||||
void push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const;
|
||||
void push(command_stream *Commands, VkPipelineLayout Layout, void const *Data) const;
|
||||
|
||||
private:
|
||||
std::vector<VkDescriptorSetLayoutBinding> m_bindings;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
157
rendering/vulkan14/vulkan14grass.cpp
Normal file
157
rendering/vulkan14/vulkan14grass.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14grass.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
grass_field::~grass_field()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void grass_field::init(resources const &Resources)
|
||||
{
|
||||
m_resources = &Resources;
|
||||
}
|
||||
|
||||
void grass_field::destroy()
|
||||
{
|
||||
if (m_resources == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the images go with the chunks; the device is idle
|
||||
clear();
|
||||
m_chunks.clear();
|
||||
m_resources = nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<image> grass_field::make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, char const *Name)
|
||||
{
|
||||
image_desc desc;
|
||||
desc.format = Format;
|
||||
desc.extent = {Width, Height, 1};
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
auto result{std::make_shared<image>()};
|
||||
if (false == result->create(*m_resources->memory, desc, Name))
|
||||
{
|
||||
ErrorLog(std::string("heightmap terrain: no gpu image for the ") + Name);
|
||||
return nullptr;
|
||||
}
|
||||
if (false == m_resources->uploads->upload(result, Data, Size, {{0, {Width, Height, 1}}}, false))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void grass_field::gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source)
|
||||
{
|
||||
if (m_resources == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &chunk{m_chunks[Id]};
|
||||
auto const side{static_cast<std::uint32_t>(Source.cells + 1)};
|
||||
chunk.images[0] = make_image(VK_FORMAT_R32_SFLOAT, side, side, Source.heights, static_cast<VkDeviceSize>(side) * side * sizeof(float), "grass heights");
|
||||
chunk.samplers[0] = m_resources->sampling->clamp_nearest;
|
||||
|
||||
// the densities of the kinds side by side, as the channels of a texel
|
||||
auto const samples{static_cast<std::uint32_t>(Source.samples + 1)};
|
||||
auto const plane{static_cast<std::size_t>(samples) * samples};
|
||||
std::size_t kinds{0};
|
||||
while (kinds < Source.kinds.size() && Source.kinds[kinds] >= 0)
|
||||
++kinds;
|
||||
std::vector<std::uint8_t> texels(plane * 4, 0);
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
for (std::size_t i = 0; i < plane; ++i)
|
||||
texels[i * 4 + kind] = Source.density[kind * plane + i];
|
||||
chunk.images[1] = make_image(VK_FORMAT_R8G8B8A8_UNORM, samples, samples, texels.data(), texels.size(), "grass densities");
|
||||
chunk.samplers[1] = m_resources->sampling->clamp_linear;
|
||||
|
||||
std::uint8_t const none{0};
|
||||
auto const holeside{static_cast<std::uint32_t>((Source.holes > 0 && Source.hole_mask != nullptr) ? Source.holes + 1 : 1)};
|
||||
chunk.images[2] = make_image(VK_FORMAT_R8_UNORM, holeside, holeside, holeside > 1 ? Source.hole_mask : &none, static_cast<VkDeviceSize>(holeside) * holeside, "grass holes");
|
||||
chunk.samplers[2] = m_resources->sampling->clamp_linear;
|
||||
// the table is made when the chunk is drawn
|
||||
chunk.images[3].reset();
|
||||
chunk.samplers[3] = m_resources->sampling->clamp_nearest;
|
||||
chunk.table_made = false;
|
||||
}
|
||||
|
||||
void grass_field::gpu_release(std::uint64_t const Id)
|
||||
{
|
||||
auto const lookup{m_chunks.find(Id)};
|
||||
if (lookup == m_chunks.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_resources != nullptr)
|
||||
{
|
||||
// the frames in flight may still read them
|
||||
m_resources->deletions->push(m_resources->uploads->current_value(), [images = std::move(lookup->second.images)]() {});
|
||||
}
|
||||
m_chunks.erase(lookup);
|
||||
}
|
||||
|
||||
grass_field::chunk_images const *grass_field::images(chunk_draw const &Draw)
|
||||
{
|
||||
auto const lookup{m_chunks.find(Draw.id)};
|
||||
if ((lookup == m_chunks.end()) || (m_resources == nullptr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto &chunk{lookup->second};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
if (chunk.images[i] == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// the kinds of the chunk, by columns: base, tip, shape, dry; the share and the chunk in the last row
|
||||
std::array<glm::vec4, 20> table{};
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
table[0 * 4 + i] = Draw.base[i];
|
||||
table[1 * 4 + i] = Draw.tip[i];
|
||||
table[2 * 4 + i] = Draw.shape[i];
|
||||
table[3 * 4 + i] = Draw.dry[i];
|
||||
}
|
||||
table[4 * 4 + 0] = Draw.share;
|
||||
table[4 * 4 + 1] = Draw.chunk;
|
||||
if ((false == chunk.table_made) || (std::memcmp(table.data(), chunk.table.data(), sizeof(table)) != 0) || (chunk.images[3] == nullptr))
|
||||
{
|
||||
auto map{chunk.images[3]};
|
||||
if (map == nullptr)
|
||||
{
|
||||
map = make_image(VK_FORMAT_R32G32B32A32_SFLOAT, 4, 5, table.data(), sizeof(table), "grass kinds");
|
||||
}
|
||||
else if (false == m_resources->uploads->upload(map, table.data(), sizeof(table), {{0, {4, 5, 1}}}, false))
|
||||
{
|
||||
map.reset();
|
||||
}
|
||||
if (map == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
chunk.images[3] = map;
|
||||
chunk.table = table;
|
||||
chunk.table_made = true;
|
||||
}
|
||||
return &chunk;
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
60
rendering/vulkan14/vulkan14grass.h
Normal file
60
rendering/vulkan14/vulkan14grass.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 grass of the heightmap terrain in the vulkan renderer (the stage P6 of the plan, opengl46_grass): the images of the chunks
|
||||
// of gfx::grass_field, uploaded when a chunk comes near. what the opengl renderer gives grass.vert in uniforms goes to the push
|
||||
// constants (the tile and the frame) and to a table of the chunk (its kinds, an image the vertex shader reads), which is made
|
||||
// again when what it holds changes
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/grassfield.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class grass_field : public gfx::grass_field
|
||||
{
|
||||
public:
|
||||
// the images a chunk is drawn with, in the order of the bindings of grass.vert: heights, densities, holes, the table
|
||||
struct chunk_images
|
||||
{
|
||||
std::array<std::shared_ptr<image>, 4> images;
|
||||
std::array<VkSampler, 4> samplers{};
|
||||
std::array<glm::vec4, 20> table{}; // what the table holds
|
||||
bool table_made{false};
|
||||
};
|
||||
|
||||
grass_field() = default;
|
||||
~grass_field() override;
|
||||
|
||||
// called once, before anything is made
|
||||
void init(resources const &Resources);
|
||||
// the images go (the device is idle)
|
||||
void destroy();
|
||||
// the images of a chunk drawn, its table made for the draw first; null: the chunk has none (yet)
|
||||
chunk_images const *images(chunk_draw const &Draw);
|
||||
|
||||
protected:
|
||||
void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) override;
|
||||
void gpu_release(std::uint64_t const Id) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<image> make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, char const *Name);
|
||||
|
||||
resources const *m_resources{nullptr};
|
||||
std::unordered_map<std::uint64_t, chunk_images> m_chunks;
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14imgui.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14bindings.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "imgui/imgui.h"
|
||||
@@ -216,7 +217,7 @@ void imgui_backend::Release_Image(std::uint64_t const Image)
|
||||
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
|
||||
}
|
||||
|
||||
void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
|
||||
void imgui_backend::record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
|
||||
{
|
||||
auto const *drawdata{m_drawdata};
|
||||
m_drawdata = nullptr;
|
||||
@@ -270,10 +271,10 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
|
||||
indexdata += commands->IdxBuffer.Size * sizeof(ImDrawIdx);
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(Commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
Commands->bind_pipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
set_draw_state(Commands, Extent);
|
||||
vkCmdBindVertexBuffers(Commands, 0, 1, &vertices.buffer, &vertices.offset);
|
||||
vkCmdBindIndexBuffer(Commands, indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
|
||||
Commands->bind_vertex_buffers(0, 1, &vertices.buffer, &vertices.offset);
|
||||
Commands->bind_index_buffer(indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
|
||||
|
||||
// the pixels of the display (top left origin, y down) to the clip space of vulkan (y down too)
|
||||
float transform[4];
|
||||
@@ -288,7 +289,7 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
|
||||
push.offset = 0;
|
||||
push.size = sizeof(transform);
|
||||
push.pValues = transform;
|
||||
vkCmdPushConstants2(Commands, &push);
|
||||
Commands->push_constants2(&push);
|
||||
|
||||
auto const clipoffset{drawdata->DisplayPos};
|
||||
std::uint32_t vertexoffset{0}, indexoffset{0};
|
||||
@@ -324,14 +325,14 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
|
||||
continue;
|
||||
}
|
||||
VkRect2D scissor{{static_cast<std::int32_t>(minx), static_cast<std::int32_t>(miny)}, {static_cast<std::uint32_t>(maxx - minx), static_cast<std::uint32_t>(maxy - miny)}};
|
||||
vkCmdSetScissor(Commands, 0, 1, &scissor);
|
||||
Commands->set_scissor(0, 1, &scissor);
|
||||
if (pushed != command.GetTexID())
|
||||
{
|
||||
VkDescriptorImageInfo info{entry->sampler, entry->target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
m_set.push(Commands, m_layout, &info);
|
||||
pushed = command.GetTexID();
|
||||
}
|
||||
vkCmdDrawIndexed(Commands, command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
|
||||
Commands->draw_indexed(command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
|
||||
}
|
||||
vertexoffset += static_cast<std::uint32_t>(commands->VtxBuffer.Size);
|
||||
indexoffset += static_cast<std::uint32_t>(commands->IdxBuffer.Size);
|
||||
|
||||
@@ -50,7 +50,7 @@ class imgui_backend : public imgui_renderer
|
||||
}
|
||||
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
|
||||
// image encodes the colour itself
|
||||
void record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
|
||||
void record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
|
||||
// the frame of imgui is dropped (nothing to draw it to)
|
||||
void skip()
|
||||
{
|
||||
|
||||
822
rendering/vulkan14/vulkan14indirect.cpp
Normal file
822
rendering/vulkan14/vulkan14indirect.cpp
Normal file
@@ -0,0 +1,822 @@
|
||||
/*
|
||||
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 instances drawn from the gpu culling in the vulkan renderer (the stage P5 of the plan; opengl46_renderer::Render_Instances_Indirect()
|
||||
// and the rest): the cullings of the passes picked as the opengl renderer picks them, the rendering of the pass ended for the compute
|
||||
// passes and begun again where it was, the walks of the culled groups shared with the opengl renderer (gfx::scene_walk), and the indirect
|
||||
// draws of the recorded draws, with multi draws where they follow one another with the same state
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
|
||||
|
||||
} // namespace
|
||||
|
||||
// the cullings
|
||||
|
||||
bool vulkan14_renderer::init_cull()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &cull{scene.cull};
|
||||
auto const device{ctx.device.handle()};
|
||||
vk14::cull_environment environment;
|
||||
environment.memory = &ctx.memory;
|
||||
environment.uploads = &ctx.uploads;
|
||||
environment.deletions = &ctx.deletions;
|
||||
environment.ring = &ctx.ring;
|
||||
environment.uniform_alignment = ctx.device.caps().limits.minUniformBufferOffsetAlignment;
|
||||
environment.storage_alignment = ctx.device.caps().limits.minStorageBufferOffsetAlignment;
|
||||
environment.frames = static_cast<std::uint32_t>(ctx.frames.size());
|
||||
environment.no_pyramid = vk14::image_descriptor(ctx.sampling.clamp_nearest, scene.depth_empty->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
if (false == cull.main.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto &cascade : cull.cascades)
|
||||
{
|
||||
if (false == cascade.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout, &cull.main))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((false == cull.reflections.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout, &cull.main)) || (false == cull.hiz.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cull.main.begin_frame(ctx.slot, ctx.timelinevalue + 1, ctx.completed());
|
||||
cull.ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::destroy_cull()
|
||||
{
|
||||
if (!m_context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &cull{m_context->scene.cull};
|
||||
auto const device{m_context->device.handle()};
|
||||
// the ones which share the kernels of the main view first
|
||||
for (auto &cascade : cull.cascades)
|
||||
{
|
||||
cascade.destroy(device);
|
||||
}
|
||||
for (auto *culls : {&cull.headlights, &cull.lamps})
|
||||
{
|
||||
for (auto &culling : *culls)
|
||||
{
|
||||
if (culling)
|
||||
{
|
||||
culling->destroy(device);
|
||||
}
|
||||
}
|
||||
culls->clear();
|
||||
}
|
||||
cull.reflections.destroy(device);
|
||||
cull.main.destroy(device);
|
||||
cull.hiz.destroy(device);
|
||||
cull.shadowfit.destroy(device);
|
||||
cull.ready = false;
|
||||
}
|
||||
|
||||
vk14::instance_cull &vulkan14_renderer::pass_cull()
|
||||
{
|
||||
return static_cast<vk14::instance_cull &>(*m_passcull);
|
||||
}
|
||||
|
||||
// the gpu culling runs along the first color pass of the main viewport of a frame, and the passes of the cascades of the sun
|
||||
// shadows and of the faces of the environment map drawn for it, each with a culling of its own (opengl46_renderer::walk_region_begin())
|
||||
void vulkan14_renderer::walk_region_begin()
|
||||
{
|
||||
vk14::instance_cull *passcull{nullptr};
|
||||
auto &cull{m_context->scene.cull};
|
||||
if ((Global.gfx_gpucull != 0) && (m_current_viewport != nullptr) && (m_current_viewport->main) && m_draw->rendering)
|
||||
{
|
||||
static bool tried{false};
|
||||
if ((false == cull.ready) && (false == tried))
|
||||
{
|
||||
tried = true;
|
||||
if (false == init_cull())
|
||||
{
|
||||
ErrorLog("vulkan: the gpu culling of the instances can't be used, the cpu culls them (gfx.gpucull)");
|
||||
destroy_cull();
|
||||
}
|
||||
}
|
||||
if (cull.ready)
|
||||
{
|
||||
if ((m_renderpass.draw_mode == rendermode::color) && (m_instancecullframe != m_framestamp))
|
||||
{
|
||||
m_instancecullframe = m_framestamp;
|
||||
passcull = &cull.main;
|
||||
}
|
||||
else if ((m_renderpass.draw_mode == rendermode::shadows) && (m_lightshadowcull != nullptr))
|
||||
{
|
||||
// the map of a set of headlights or the scenery part of the map of a lamp, which picked its culling
|
||||
passcull = m_lightshadowcull;
|
||||
m_shadowcasters.clear();
|
||||
}
|
||||
else if ((m_renderpass.draw_mode == rendermode::shadows) && Global.gfx_gpucull_shadows && (m_shadowcascade >= 0) && (m_shadowcascade < static_cast<int>(cull.cascades.size())) &&
|
||||
(false == m_cubeshadows))
|
||||
{
|
||||
// the cascades of the sun
|
||||
passcull = &cull.cascades[m_shadowcascade];
|
||||
m_shadowcasters.clear();
|
||||
}
|
||||
else if ((m_renderpass.draw_mode == rendermode::reflections) && Global.gfx_gpucull_reflections && (Global.reflectiontune.fidelity >= 1))
|
||||
{
|
||||
passcull = &cull.reflections;
|
||||
}
|
||||
}
|
||||
}
|
||||
Begin_Pass_Cull(passcull, (passcull == &cull.main));
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::walk_instances_begin()
|
||||
{
|
||||
if (m_indirectcolourpending)
|
||||
{
|
||||
// the instances culled and recorded by the depth pass of the whole scene, the buckets weren't queued for the cpu
|
||||
render_instances_indirect_pending();
|
||||
}
|
||||
if (m_instancecullpass)
|
||||
{
|
||||
if (m_passcull->drawing())
|
||||
{
|
||||
// the buckets of the pass weren't queued for the cpu, which leaves the loop of the walk empty
|
||||
if (m_passcull == &m_context->scene.cull.main)
|
||||
{
|
||||
render_instances_indirect();
|
||||
}
|
||||
else
|
||||
{
|
||||
render_instances_indirect_pass();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the view of the pass is in place here, the one Render_Instanced() takes
|
||||
suspend_rendering();
|
||||
pass_cull().dispatch(m_context->commands, OpenGLMatrices.data(GL_MODELVIEW));
|
||||
resume_rendering();
|
||||
// what the cpu drew of the sleepers and the models drawn on their own
|
||||
Compare_Pass_Cull();
|
||||
}
|
||||
}
|
||||
return m_instancecullpass;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::walk_instances_end()
|
||||
{
|
||||
End_Pass_Cull();
|
||||
}
|
||||
|
||||
// the rendering ended and begun again around the compute passes
|
||||
|
||||
void vulkan14_renderer::remember_rendering(VkRenderingInfo const &Info, std::vector<vk14::image_use> const &Uses)
|
||||
{
|
||||
auto &resume{m_draw->resume};
|
||||
resume.info = Info;
|
||||
resume.info.pNext = nullptr;
|
||||
resume.info.flags = 0;
|
||||
auto const colours{std::min<std::uint32_t>(Info.colorAttachmentCount, static_cast<std::uint32_t>(resume.colours.size()))};
|
||||
for (std::uint32_t i = 0; i < colours; ++i)
|
||||
{
|
||||
resume.colours[i] = Info.pColorAttachments[i];
|
||||
// what was drawn is drawn over
|
||||
resume.colours[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
}
|
||||
resume.info.colorAttachmentCount = colours;
|
||||
resume.info.pColorAttachments = (colours > 0 ? resume.colours.data() : nullptr);
|
||||
resume.has_depth = (Info.pDepthAttachment != nullptr);
|
||||
if (resume.has_depth)
|
||||
{
|
||||
resume.depth = *Info.pDepthAttachment;
|
||||
resume.depth.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
}
|
||||
resume.info.pDepthAttachment = (resume.has_depth ? &resume.depth : nullptr);
|
||||
resume.info.pStencilAttachment = nullptr;
|
||||
resume.uses = Uses;
|
||||
resume.valid = true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::suspend_rendering()
|
||||
{
|
||||
auto &draw{*m_draw};
|
||||
if (false == draw.rendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_context->commands->end_rendering();
|
||||
draw.rendering = false;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::resume_rendering()
|
||||
{
|
||||
auto &draw{*m_draw};
|
||||
auto &resume{draw.resume};
|
||||
if (draw.rendering || (false == resume.valid) || (draw.targets == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const commands{m_context->commands};
|
||||
// what the rendering stored before is loaded again
|
||||
vk14::barriers(commands, resume.uses);
|
||||
commands->begin_rendering(&resume.info);
|
||||
// the dynamic state starts anew with the rendering, the state of the walk goes on
|
||||
auto const set{draw.set};
|
||||
auto const shadows{draw.shadows};
|
||||
auto const blending{draw.blending};
|
||||
auto const cullface{draw.cullface};
|
||||
auto const glare{draw.glare};
|
||||
auto const depthmask_off{draw.depthmask_off};
|
||||
auto *program{draw.program};
|
||||
auto const textures{draw.textures};
|
||||
start_draws(*draw.targets, static_cast<int>(draw.extent.width), static_cast<int>(draw.extent.height), set, shadows);
|
||||
draw.blending = blending;
|
||||
draw.cullface = cullface;
|
||||
draw.glare = glare;
|
||||
draw.depthmask_off = depthmask_off;
|
||||
draw.program = program;
|
||||
draw.textures = textures;
|
||||
if (draw.depthpass)
|
||||
{
|
||||
// the bias of the depth pass of the scene (render_scene_depth())
|
||||
commands->set_depth_bias_enable(VK_TRUE);
|
||||
commands->set_depth_bias(-1.f, 0.f, -1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// the draws
|
||||
|
||||
vk14::scene_program *vulkan14_renderer::multidraw_program(vk14::scene_program &Program)
|
||||
{
|
||||
if (Program.multidraw_tried)
|
||||
{
|
||||
return Program.multidraw.get();
|
||||
}
|
||||
Program.multidraw_tried = true;
|
||||
if (Program.vertex_name.empty() || Program.fragment_name.empty() || (Program.vertices != vk14::vertex_kind::scene))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto &ctx{*m_context};
|
||||
try
|
||||
{
|
||||
auto variant{std::make_shared<vk14::scene_program>()};
|
||||
variant->defines = Program.defines + "#define MODEL_DRAWS\n";
|
||||
variant->vertex = ctx.shaders.get(Program.vertex_name, variant->defines);
|
||||
variant->fragment = ctx.shaders.get(Program.fragment_name, variant->defines);
|
||||
variant->vertex_name = Program.vertex_name;
|
||||
variant->fragment_name = Program.fragment_name;
|
||||
variant->vertices = Program.vertices;
|
||||
variant->name = Program.name + " (multi draws)";
|
||||
variant->multidraw_tried = true;
|
||||
Program.multidraw = std::move(variant);
|
||||
}
|
||||
catch (gfx::shader_exception const &Exception)
|
||||
{
|
||||
vk14::log_error(Exception.what());
|
||||
}
|
||||
return Program.multidraw.get();
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::draw_indirect(vk14::instance_cull &Cull, command_set const Set, std::size_t const First, std::uint32_t const Count, vk14::scene_program &Program, vk14::blend_mode const Blend)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &draw{*m_draw};
|
||||
auto const &first{m_indirectdraws[First]};
|
||||
if ((false == draw.rendering) || (Count == 0) || (Program.vertices != vk14::vertex_kind::scene) || (Cull.command_buffer() == VK_NULL_HANDLE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const &geometry{first.geometry};
|
||||
if ((geometry.bank == 0) || (geometry.bank > m_banks.size()) || (m_banks[geometry.bank - 1] == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the buffers of the bank of the chunks, with their data on the gpu (the chunks of a multi draw are of one bank)
|
||||
gfx::indirect_source source;
|
||||
if (false == m_geometry.indirect_source(geometry, source))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const &chunk{static_cast<vk14::vulkan14_geometrybank *>(m_banks[geometry.bank - 1])->last()};
|
||||
if (false == bind_scene_pipeline(Program, *draw.targets, Blend, static_cast<int>(chunk.topology)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
apply_draw_state();
|
||||
// the blocks of the draws, one after another in the window of the blocks: the multi draw picks them by the number of the draw
|
||||
std::uint32_t block{~0u};
|
||||
if (Count == 1)
|
||||
{
|
||||
model_ubs = first.ubs;
|
||||
block = push_draw_block();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &draws{scene.draws};
|
||||
auto const size{static_cast<VkDeviceSize>(Count) * draws.stride};
|
||||
auto const slice{size <= draws.window ? draws.ring.allocate(size, draws.stride) : vk14::ring_slice{}};
|
||||
if (slice)
|
||||
{
|
||||
if ((false == draws.open) || (slice.offset + size > draws.base + draws.window))
|
||||
{
|
||||
draws.base = slice.offset;
|
||||
draws.open = true;
|
||||
draw.set_dirty = true;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < Count; ++i)
|
||||
{
|
||||
std::memcpy(slice.data + i * draws.stride, &m_indirectdraws[First + i].ubs, sizeof(gl::model_ubs));
|
||||
}
|
||||
block = static_cast<std::uint32_t>((slice.offset - draws.base) / draws.stride);
|
||||
}
|
||||
}
|
||||
// the next draw of the walk pushes a block of its own
|
||||
draw.block = ~0u;
|
||||
if (block == ~0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (draw.set_dirty)
|
||||
{
|
||||
bind_pass_set();
|
||||
}
|
||||
if (draw.textures_dirty)
|
||||
{
|
||||
push_textures();
|
||||
}
|
||||
auto const commands{draw.commands};
|
||||
if (draw.pushed_draw != block)
|
||||
{
|
||||
vk14::draw_constants constants;
|
||||
constants.draw = block;
|
||||
VkPushConstantsInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
|
||||
info.layout = scene.scene_layout;
|
||||
info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
info.offset = 0;
|
||||
info.size = sizeof(constants);
|
||||
info.pValues = &constants;
|
||||
commands->push_constants2(&info);
|
||||
draw.pushed_draw = block;
|
||||
}
|
||||
if ((chunk.vertices != draw.vertices) || (chunk.userdata != draw.userdata))
|
||||
{
|
||||
VkBuffer const buffers[2]{chunk.vertices, chunk.userdata};
|
||||
VkDeviceSize const offsets[2]{0, 0};
|
||||
commands->bind_vertex_buffers(0, 2, buffers, offsets);
|
||||
draw.vertices = chunk.vertices;
|
||||
draw.userdata = chunk.userdata;
|
||||
}
|
||||
auto const offset{static_cast<VkDeviceSize>(Cull.draw_command_offset(Set, First))};
|
||||
if (chunk.indices != VK_NULL_HANDLE)
|
||||
{
|
||||
if (chunk.indices != draw.indices)
|
||||
{
|
||||
commands->bind_index_buffer(chunk.indices, 0, VK_INDEX_TYPE_UINT32);
|
||||
draw.indices = chunk.indices;
|
||||
}
|
||||
commands->draw_indexed_indirect(Cull.command_buffer(), offset, Count, sizeof(gl::instance_draw_command));
|
||||
}
|
||||
else
|
||||
{
|
||||
commands->draw_indirect(Cull.command_buffer(), offset, Count, sizeof(gl::instance_draw_command));
|
||||
}
|
||||
draw.draws += Count;
|
||||
return true;
|
||||
}
|
||||
|
||||
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_colour(vk14::instance_cull &Cull, command_set const Set)
|
||||
{
|
||||
auto &draw{*m_draw};
|
||||
auto const &scene{m_context->scene};
|
||||
// the draws were recorded with the seed of scenery models and sleepers, 0 (TAnimModel::RaPrepare()); vehicles drawn since
|
||||
// leave their own, which would pick other texture variants than those the draws were recorded and ordered with
|
||||
TSubModel::iVariantSeed = 0;
|
||||
indirect_counts counts;
|
||||
auto const count{m_indirectdraws.size()};
|
||||
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
|
||||
for (std::size_t index = 0; index < count;)
|
||||
{
|
||||
auto const blending{m_indirectdraws[index].blending};
|
||||
if (blending)
|
||||
{
|
||||
walk_blending(true);
|
||||
}
|
||||
walk_material(m_indirectdraws[index].material, m_indirectdraws[index].submodel);
|
||||
auto *program{draw.program};
|
||||
auto *multidraw{(m_indirectmultidraw && (program != nullptr)) ? multidraw_program(*program) : nullptr};
|
||||
auto last{index + 1};
|
||||
if (multidraw != nullptr)
|
||||
{
|
||||
// the draws which follow with the same state, in one call
|
||||
while ((last < count) && (last - index < longest) && Indirect_Mergeable(index, last))
|
||||
{
|
||||
++last;
|
||||
}
|
||||
}
|
||||
if (program != nullptr)
|
||||
{
|
||||
auto const drawcount{static_cast<std::uint32_t>(last - index)};
|
||||
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : *program), (draw.blending ? vk14::blend_mode::scene_alpha : vk14::blend_mode::none)))
|
||||
{
|
||||
counts.draws += drawcount;
|
||||
}
|
||||
++counts.calls;
|
||||
}
|
||||
index = last;
|
||||
if (blending)
|
||||
{
|
||||
walk_blending(false);
|
||||
}
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
// the model block the draws made one by one leave behind
|
||||
model_ubs = m_indirectdraws.back().ubs;
|
||||
}
|
||||
model_ubs.emission = 0.0f;
|
||||
return counts;
|
||||
}
|
||||
|
||||
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_depth(vk14::instance_cull &Cull, command_set const Set)
|
||||
{
|
||||
auto &draw{*m_draw};
|
||||
auto &scene{m_context->scene};
|
||||
auto const commands{m_context->commands};
|
||||
// as in draw_indirect_colour(): the seed the draws were recorded with
|
||||
TSubModel::iVariantSeed = 0;
|
||||
indirect_counts counts;
|
||||
auto const count{m_indirectdraws.size()};
|
||||
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
|
||||
// whether the prepass of the class draws the draw, and with which diffuse texture (class 2)
|
||||
auto const prepass_texture = [this](std::size_t const Index, int const Prepassclass, texture_handle &Texture) -> bool {
|
||||
auto const &entry{m_indirectdraws[Index]};
|
||||
if ((entry.blending) || (entry.ubs.opacity < 0.f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((entry.type != GL_TRIANGLES) && (entry.type != GL_TRIANGLE_STRIP) && (entry.type != GL_TRIANGLE_FAN))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (prepass_class(entry.material) != Prepassclass)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Texture = null_handle;
|
||||
if (Prepassclass == 2)
|
||||
{
|
||||
// the diffuse texture of the colour pass, the same choice as walk_material() makes
|
||||
auto const &material{m_materials.material(entry.material)};
|
||||
auto const &textures{material.texture_variants.empty() ? material.textures : material.GetTextures(TSubModel::VariantId(entry.submodel))};
|
||||
if (textures[0] == null_handle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Texture = textures[0];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// inside the colour pass the depth goes a little farther than the surface, without the colour (the depth pass of the scene has
|
||||
// its own state)
|
||||
auto const insidecolour{false == draw.depthpass};
|
||||
if (insidecolour)
|
||||
{
|
||||
commands->set_depth_bias_enable(VK_TRUE);
|
||||
commands->set_depth_bias(-1.f, 0.f, -1.f);
|
||||
}
|
||||
walk_blending(false);
|
||||
// the draws without the alpha test first, then the ones with it, one program each
|
||||
for (auto prepassclass = 1; prepassclass <= 2; ++prepassclass)
|
||||
{
|
||||
auto &program{prepassclass == 1 ? *scene.prepass : *scene.prepass_alpha};
|
||||
auto *multidraw{m_indirectmultidraw ? multidraw_program(program) : nullptr};
|
||||
for (std::size_t index = 0; index < count;)
|
||||
{
|
||||
texture_handle texture;
|
||||
if (false == prepass_texture(index, prepassclass, texture))
|
||||
{
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
if (prepassclass == 2)
|
||||
{
|
||||
walk_texture(0, texture);
|
||||
}
|
||||
auto const &entry{m_indirectdraws[index]};
|
||||
auto last{index + 1};
|
||||
if (multidraw != nullptr)
|
||||
{
|
||||
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test. the
|
||||
// material doesn't matter to the depth
|
||||
texture_handle othertexture;
|
||||
while ((last < count) && (last - index < longest) && (m_indirectdraws[last].group == entry.group) && (m_indirectdraws[last].type == entry.type) &&
|
||||
(m_indirectcommands[last].indexed == m_indirectcommands[index].indexed) && (prepass_texture(last, prepassclass, othertexture)) && (othertexture == texture))
|
||||
{
|
||||
++last;
|
||||
}
|
||||
}
|
||||
auto const drawcount{static_cast<std::uint32_t>(last - index)};
|
||||
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : program), (insidecolour ? vk14::blend_mode::no_colour : vk14::blend_mode::none)))
|
||||
{
|
||||
counts.draws += drawcount;
|
||||
}
|
||||
++counts.calls;
|
||||
index = last;
|
||||
}
|
||||
}
|
||||
if (insidecolour)
|
||||
{
|
||||
commands->set_depth_bias_enable(VK_FALSE);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_shadow(vk14::instance_cull &Cull, command_set const Set)
|
||||
{
|
||||
auto &scene{m_context->scene};
|
||||
indirect_counts counts;
|
||||
auto const count{m_indirectdraws.size()};
|
||||
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
|
||||
for (std::size_t index = 0; index < count;)
|
||||
{
|
||||
auto const &entry{m_indirectdraws[index]};
|
||||
auto const texture{Indirect_Shadow_Texture(entry.material)};
|
||||
auto &program{m_cubeshadows ? (texture != null_handle ? *scene.cube_alpha_shadow : *scene.cube_shadow) : (texture != null_handle ? *scene.alpha_shadow : *scene.shadow)};
|
||||
auto *multidraw{m_indirectmultidraw ? multidraw_program(program) : nullptr};
|
||||
auto last{index + 1};
|
||||
if (multidraw != nullptr)
|
||||
{
|
||||
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test
|
||||
while ((last < count) && (last - index < longest) && (m_indirectdraws[last].group == entry.group) && (m_indirectdraws[last].type == entry.type) &&
|
||||
(m_indirectcommands[last].indexed == m_indirectcommands[index].indexed) && (Indirect_Shadow_Texture(m_indirectdraws[last].material) == texture))
|
||||
{
|
||||
++last;
|
||||
}
|
||||
}
|
||||
if (texture != null_handle)
|
||||
{
|
||||
walk_texture(0, texture);
|
||||
}
|
||||
auto const drawcount{static_cast<std::uint32_t>(last - index)};
|
||||
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : program), vk14::blend_mode::none))
|
||||
{
|
||||
counts.draws += drawcount;
|
||||
}
|
||||
++counts.calls;
|
||||
index = last;
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
model_ubs = m_indirectdraws.back().ubs;
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::record_indirect_draws(vk14::instance_cull &Cull, bool const Cascade)
|
||||
{
|
||||
m_indirectcull = &Cull;
|
||||
Walk_Indirect_Draws(Cull, Cascade);
|
||||
auto const orderstart{std::chrono::steady_clock::now()};
|
||||
// with multi draws the draws with the same state follow one another; the order goes into the commands, so it comes first
|
||||
m_indirectmultidraw = Global.gfx_gpucull_multidraw;
|
||||
if (m_indirectmultidraw && (false == m_indirectdraws.empty()))
|
||||
{
|
||||
Order_Indirect_Draws();
|
||||
}
|
||||
Cull.fill_commands(m_context->commands, m_indirectcommands);
|
||||
m_walkcounts.ordertime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - orderstart).count();
|
||||
}
|
||||
|
||||
// the instances of the main view: the depth pyramid of what the pass drew so far, the culling, the walks, the depth first (the
|
||||
// depth pass of the scene, gfx.depthprepass), the colour; in two phases the instances seen in the previous frame first
|
||||
void vulkan14_renderer::render_instances_indirect()
|
||||
{
|
||||
auto const cpustart{std::chrono::steady_clock::now()};
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &cull{scene.cull.main};
|
||||
auto &draw{*m_draw};
|
||||
auto const commands{ctx.commands};
|
||||
auto const viewport{static_cast<std::size_t>(m_current_viewport - m_viewports.data())};
|
||||
auto *targets{viewport < scene.targets.size() ? &scene.targets[viewport] : nullptr};
|
||||
auto const projection{OpenGLMatrices.data(GL_PROJECTION)};
|
||||
|
||||
suspend_rendering();
|
||||
// the occlusion test against the depth drawn so far in the pass: cab, terrain, shapes, tracks, vehicles, other models
|
||||
vk14::cull_occlusion occlusion;
|
||||
auto const build = [&]() {
|
||||
vk14::barriers(commands, {vk14::use_of(*targets->depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
|
||||
if (scene.cull.hiz.build(commands, ctx.device.handle(), ctx.memory, ctx.deletions, ctx.uploads.current_value(), targets->depth->view(), ctx.sampling.clamp_nearest, static_cast<int>(targets->extent.width),
|
||||
static_cast<int>(targets->extent.height)))
|
||||
{
|
||||
occlusion = scene.cull.hiz.occlusion(ctx.sampling.clamp_nearest, projection);
|
||||
}
|
||||
};
|
||||
auto const useocclusion{Global.gfx_gpucull_occlusion && (false == Global.gfx_skippipeline) && (targets != nullptr) && targets->depth};
|
||||
if (useocclusion)
|
||||
{
|
||||
build();
|
||||
}
|
||||
// in two phases the instances seen in the previous frame are drawn first, and the others are tested against a pyramid made
|
||||
// again with their depth in it (not in wireframe, which doesn't draw surfaces)
|
||||
cull.dispatch(commands, OpenGLMatrices.data(GL_MODELVIEW), (occlusion.pyramid != VK_NULL_HANDLE ? &occlusion : nullptr), (Global.gfx_gpucull_occlusion_instances && (false == Global.bWireFrame)));
|
||||
// the draws, recorded while the gpu culls
|
||||
record_indirect_draws(cull, false);
|
||||
|
||||
auto const twophase{cull.two_phase()};
|
||||
auto const draws{false == m_indirectdraws.empty()};
|
||||
// the depth comes first in the depth pass of the whole scene, which leaves the colour to the colour pass, and with the depth
|
||||
// prepass of the instances
|
||||
auto const depthfirst{draw.depthpass || (Global.gfx_depthprepass && (false == Global.bWireFrame))};
|
||||
indirect_counts prepasscounts;
|
||||
indirect_counts colourcounts;
|
||||
auto colourset{command_set::first};
|
||||
resume_rendering();
|
||||
if (twophase)
|
||||
{
|
||||
if (draws)
|
||||
{
|
||||
// the instances seen in the previous frame, then the pyramid again, with them in it
|
||||
use_cull_pass_sets(&cull);
|
||||
if (depthfirst)
|
||||
{
|
||||
prepasscounts += draw_indirect_depth(cull, command_set::first);
|
||||
}
|
||||
else
|
||||
{
|
||||
colourcounts += draw_indirect_colour(cull, command_set::first);
|
||||
}
|
||||
use_cull_pass_sets(nullptr);
|
||||
}
|
||||
suspend_rendering();
|
||||
if (draws)
|
||||
{
|
||||
build();
|
||||
}
|
||||
// the others are tested against it; with no draws the instances are only marked for the next frame
|
||||
cull.dispatch_second(commands, occlusion);
|
||||
resume_rendering();
|
||||
colourset = (depthfirst ? command_set::all : command_set::second);
|
||||
}
|
||||
if (draws)
|
||||
{
|
||||
use_cull_pass_sets(&cull);
|
||||
if (depthfirst)
|
||||
{
|
||||
prepasscounts += draw_indirect_depth(cull, (twophase ? command_set::second : command_set::first));
|
||||
}
|
||||
if (false == draw.depthpass)
|
||||
{
|
||||
colourcounts += draw_indirect_colour(cull, colourset);
|
||||
}
|
||||
use_cull_pass_sets(nullptr);
|
||||
}
|
||||
if (draw.depthpass)
|
||||
{
|
||||
// the colour pass takes the draws from here, and counts them
|
||||
m_indirectcolourpending = true;
|
||||
m_indirectcolourset = colourset;
|
||||
m_indirectcoloursleepers = m_sleeperspass;
|
||||
m_indirectcolourmodels = m_modelspass;
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>(prepasscounts.calls);
|
||||
colourcounts.draws = static_cast<std::uint32_t>(m_indirectdraws.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(colourcounts.calls);
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>(prepasscounts.calls);
|
||||
// the count of the frame the gpu reported last
|
||||
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
|
||||
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
|
||||
}
|
||||
cull.draw_result(colourcounts.draws, prepasscounts.draws, colourcounts.calls, prepasscounts.calls, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
|
||||
}
|
||||
|
||||
// the instances of a pass other than the main view: the culling of the pass with its own camera, no occlusion test, the draws
|
||||
// recorded the same way, then drawn once with the programs the pass draws with on the cpu
|
||||
void vulkan14_renderer::render_instances_indirect_pass()
|
||||
{
|
||||
auto const cpustart{std::chrono::steady_clock::now()};
|
||||
auto &cull{pass_cull()};
|
||||
auto &cascades{m_context->scene.cull.cascades};
|
||||
suspend_rendering();
|
||||
cull.dispatch(m_context->commands, OpenGLMatrices.data(GL_MODELVIEW));
|
||||
record_indirect_draws(cull, (&cull >= cascades.data()) && (&cull < cascades.data() + cascades.size()));
|
||||
resume_rendering();
|
||||
|
||||
indirect_counts counts;
|
||||
if (false == m_indirectdraws.empty())
|
||||
{
|
||||
use_cull_pass_sets(&cull);
|
||||
counts = (m_renderpass.draw_mode == rendermode::shadows ? draw_indirect_shadow(cull, command_set::first) : draw_indirect_colour(cull, command_set::first));
|
||||
use_cull_pass_sets(nullptr);
|
||||
}
|
||||
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(counts.calls);
|
||||
// the count of the frame the gpu reported last
|
||||
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
|
||||
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
|
||||
cull.draw_result(counts.draws, 0, counts.calls, 0, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
|
||||
}
|
||||
|
||||
// the colour of the instances culled and recorded by the depth pass of the whole scene
|
||||
void vulkan14_renderer::render_instances_indirect_pending()
|
||||
{
|
||||
m_indirectcolourpending = false;
|
||||
auto &cull{m_context->scene.cull.main};
|
||||
indirect_counts colourcounts;
|
||||
if (false == m_indirectdraws.empty())
|
||||
{
|
||||
auto const cpustart{std::chrono::steady_clock::now()};
|
||||
use_cull_pass_sets(&cull);
|
||||
colourcounts = draw_indirect_colour(cull, m_indirectcolourset);
|
||||
use_cull_pass_sets(nullptr);
|
||||
cull.draw_result(colourcounts.draws, cull.prepass_draws(), colourcounts.calls, cull.prepass_calls(), cull.cpu_time() + std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
|
||||
}
|
||||
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(colourcounts.calls);
|
||||
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
|
||||
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
|
||||
}
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (opengl46_renderer::Shadow_Fit())
|
||||
|
||||
char const *vulkan14_renderer::shadow_fit_unavailable(viewport_config const &Viewport) const
|
||||
{
|
||||
auto const shadowviews{std::count_if(m_viewports.begin(), m_viewports.end(), [](viewport_config const &View) { return View.shadow; })};
|
||||
return gfx::shadow_fit::unavailable(false == Global.gfx_skippipeline, Viewport.shadow, static_cast<std::size_t>(shadowviews));
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::shadow_fit(viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits, gfx::shadow_fit::areas &Areas)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &cull{ctx.scene.cull};
|
||||
auto const *unavailable{Global.gfx_shadow_sdsm ? shadow_fit_unavailable(Viewport) : nullptr};
|
||||
if (Global.gfx_shadow_sdsm && (unavailable == nullptr))
|
||||
{
|
||||
if (cull.shadowfit)
|
||||
{
|
||||
// takes in what came back
|
||||
cull.shadowfit.latest(ctx.completed());
|
||||
}
|
||||
else
|
||||
{
|
||||
unavailable = "no compute passes";
|
||||
}
|
||||
}
|
||||
return cull.shadowfit.fit(m_framestamp, m_colorpass.pass_camera, unavailable, Splits, Areas, m_shadowfitstate);
|
||||
}
|
||||
|
||||
void vulkan14_renderer::measure_shadow_fit(std::size_t const Viewport)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &cull{scene.cull};
|
||||
auto const &vp{m_viewports[Viewport]};
|
||||
if ((false == vp.main) || (false == Global.gfx_shadow_sdsm) || (shadow_fit_unavailable(vp) != nullptr) || (Viewport >= scene.targets.size()) || (false == bool(scene.targets[Viewport].depth)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ((false == bool(cull.shadowfit)) && (false == cull.shadowfit_tried))
|
||||
{
|
||||
cull.shadowfit_tried = true;
|
||||
if (false == cull.shadowfit.init(ctx.device.handle(), ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, static_cast<std::uint32_t>(ctx.frames.size())))
|
||||
{
|
||||
ErrorLog("vulkan: the cascades of the sun shadows can't be fitted to the depth of the view (gfx.shadow.sdsm)");
|
||||
cull.shadowfit.destroy(ctx.device.handle());
|
||||
}
|
||||
}
|
||||
if (false == bool(cull.shadowfit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &targets{scene.targets[Viewport]};
|
||||
auto const commands{ctx.commands};
|
||||
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
|
||||
gfx::shadow_fit::view const view{m_colorpass.pass_camera.position(), m_colorpass.pass_camera.projection(), glm::mat3{m_colorpass.pass_camera.modelview()},
|
||||
gfx::shadow_fit::light_rotation(m_sunlight.direction), gfx::shadow_fit::fixed_splits().back()};
|
||||
ctx.passes.begin_pass("sun shadows fitted to the depth");
|
||||
cull.shadowfit.measure(commands, targets.depth->view(), ctx.sampling.clamp_nearest, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), view, m_framestamp, ctx.slot, ctx.timelinevalue + 1);
|
||||
ctx.passes.end_pass();
|
||||
}
|
||||
359
rendering/vulkan14/vulkan14lamps.cpp
Normal file
359
rendering/vulkan14/vulkan14lamps.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
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
|
||||
void vulkan14_renderer::begin_local_shadow_rendering()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto const &target{scene.lamps.target()};
|
||||
auto const commands{ctx.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)};
|
||||
vk14::barriers(commands, {use});
|
||||
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<int>(target.size), static_cast<int>(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_context->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<vk14::image_use> 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_context->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};
|
||||
if (false == lamps.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lamp_local(), m_framestamp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_context->passes.begin_pass("lamp shadows");
|
||||
// the cab drawn by the shadow pass, 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();
|
||||
m_renderpass.draw_stats = {};
|
||||
OpenGLMatrices.mode(GL_MODELVIEW);
|
||||
OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.lamp()}) * glm::inverse(vehicle->mMatrix)});
|
||||
lamps.begin();
|
||||
begin_local_shadow_rendering();
|
||||
m_cubeshadows = true;
|
||||
Render_cab(vehicle, 0.0f, false);
|
||||
m_cubeshadows = false;
|
||||
end_local_shadow_rendering();
|
||||
lamps.end(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
|
||||
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<double>(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();
|
||||
m_cubeshadows = true;
|
||||
if (Vehicles)
|
||||
{
|
||||
for (auto *vehicle : lamps.slot_vehicles())
|
||||
{
|
||||
Render(vehicle);
|
||||
}
|
||||
if (Cab != nullptr)
|
||||
{
|
||||
Render_cab(Cab, 0.0f, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cubescenery = true;
|
||||
m_cubeskipinstance = static_cast<TAnimModel const *>(slot.owner);
|
||||
// the instances culled on the gpu, each scenery part drawn in a frame with a culling of its own
|
||||
m_lightshadowcull = light_shadow_cull(m_context->scene.cull.lamps, m_lampscenerydrawn++, Global.gfx_gpucull_shadows_lamps);
|
||||
Render(simulation::Region);
|
||||
m_lightshadowcull = nullptr;
|
||||
m_cubeskipinstance = nullptr;
|
||||
m_cubescenery = false;
|
||||
}
|
||||
m_cubeshadows = false;
|
||||
end_local_shadow_rendering();
|
||||
lamps.end_lamp(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
|
||||
m_renderpass = m_colorpass;
|
||||
}
|
||||
|
||||
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();
|
||||
m_shadowskipvehicle = Light.owner;
|
||||
// the instances culled on the gpu, each slot with a culling of its own
|
||||
m_lightshadowcull = light_shadow_cull(m_context->scene.cull.headlights, Slot, Global.gfx_gpucull_shadows_headlights);
|
||||
Render(simulation::Region);
|
||||
m_lightshadowcull = nullptr;
|
||||
if ((Cab != nullptr) && (Cab != Light.owner) && (glm::length(Cab->GetPosition() - projector) <= reach + Cab->radius()))
|
||||
{
|
||||
Render_cab(Cab, 0.0f, false);
|
||||
}
|
||||
m_shadowskipvehicle = nullptr;
|
||||
end_local_shadow_rendering();
|
||||
lamps.end_headlight(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
|
||||
scene_ubs.projection = colorprojection;
|
||||
m_renderpass = m_colorpass;
|
||||
}
|
||||
|
||||
vk14::instance_cull *vulkan14_renderer::light_shadow_cull(std::vector<std::unique_ptr<vk14::instance_cull>> &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::instance_cull>()};
|
||||
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();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14instance.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
@@ -278,14 +279,28 @@ void image::destroy()
|
||||
state = image_state{};
|
||||
}
|
||||
|
||||
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
|
||||
void transition(command_stream *Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
|
||||
{
|
||||
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
|
||||
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
auto const &from{Image.state};
|
||||
// reads after reads in the same layout need nothing
|
||||
// reads after reads in the same layout need nothing, unless they are in stages the barrier after the last write didn't wait
|
||||
// for: those wait for the stages which read the image already (which came after the write)
|
||||
if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0))
|
||||
{
|
||||
if ((Stage & ~from.stage) != 0)
|
||||
{
|
||||
VkMemoryBarrier2 chain{};
|
||||
chain.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
|
||||
chain.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
|
||||
chain.dstStageMask = Stage;
|
||||
chain.dstAccessMask = Access;
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.memoryBarrierCount = 1;
|
||||
dependency.pMemoryBarriers = &chain;
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
Image.state.stage |= Stage;
|
||||
Image.state.access |= Access;
|
||||
return;
|
||||
@@ -307,7 +322,7 @@ void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, Vk
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
|
||||
Image.state = image_state{Layout, Stage, Access};
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ class image
|
||||
|
||||
// a barrier of an image from its state to the given one, the state of the image updated. nothing when the image already is
|
||||
// in the layout and was only read by the same stage
|
||||
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
|
||||
void transition(command_stream *Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
|
||||
|
||||
// objects destroyed once the gpu is done with the frame which used them last
|
||||
class deletion_queue
|
||||
|
||||
342
rendering/vulkan14/vulkan14particles.cpp
Normal file
342
rendering/vulkan14/vulkan14particles.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14particles.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the push constants of particles_smoke_simulate.comp (std430)
|
||||
struct simulate_constants
|
||||
{
|
||||
std::uint32_t places;
|
||||
std::uint32_t pad0[3];
|
||||
glm::vec4 step;
|
||||
glm::vec4 wind;
|
||||
std::uint32_t seed;
|
||||
std::uint32_t pad1[3];
|
||||
};
|
||||
static_assert(offsetof(simulate_constants, step) == 16 && offsetof(simulate_constants, seed) == 48, "the layout of the constants of particles_smoke_simulate.comp");
|
||||
|
||||
// the push constants of particles_smoke_prepare.comp (std430)
|
||||
struct prepare_constants
|
||||
{
|
||||
std::uint32_t places;
|
||||
std::uint32_t pad0[3];
|
||||
glm::vec3 origin;
|
||||
float pad1;
|
||||
glm::mat4 view;
|
||||
glm::vec4 options;
|
||||
glm::vec4 planes[5];
|
||||
};
|
||||
static_assert(offsetof(prepare_constants, origin) == 16 && offsetof(prepare_constants, view) == 32 && offsetof(prepare_constants, options) == 96 && offsetof(prepare_constants, planes) == 112,
|
||||
"the layout of the constants of particles_smoke_prepare.comp");
|
||||
|
||||
// the block of particles_precipitation.comp (std140)
|
||||
struct precipitation_block
|
||||
{
|
||||
glm::uvec4 counts;
|
||||
glm::vec4 offsets[3];
|
||||
glm::vec4 sizes;
|
||||
glm::vec4 velocity;
|
||||
glm::vec4 timing;
|
||||
glm::mat4 view;
|
||||
glm::vec4 options;
|
||||
glm::vec4 planes[5];
|
||||
};
|
||||
static_assert(offsetof(precipitation_block, view) == 112 && offsetof(precipitation_block, planes) == 192 && sizeof(precipitation_block) == 272, "the layout of the block of particles_precipitation.comp");
|
||||
|
||||
std::uint32_t groups(std::uint32_t const Count)
|
||||
{
|
||||
return (Count + gl::PARTICLE_GROUP_SIZE - 1) / gl::PARTICLE_GROUP_SIZE;
|
||||
}
|
||||
|
||||
VkBufferUsageFlags constexpr storage_usage{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
|
||||
|
||||
} // namespace
|
||||
|
||||
gpu_particles::~gpu_particles()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void gpu_particles::init(VkDevice const Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout const Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions,
|
||||
frame_ring &Ring, VkDeviceSize const Alignment, std::uint32_t const Frames, host Host)
|
||||
{
|
||||
m_device = Device;
|
||||
m_pipelines = &Pipelines;
|
||||
m_shaders = &Shaders;
|
||||
m_passlayout = Passlayout;
|
||||
m_memory = &Memory;
|
||||
m_uploads = &Uploads;
|
||||
m_deletions = &Deletions;
|
||||
m_ring = &Ring;
|
||||
m_alignment = std::max<VkDeviceSize>(Alignment, 16);
|
||||
m_frames = Frames;
|
||||
m_host = std::move(Host);
|
||||
}
|
||||
|
||||
void gpu_particles::destroy()
|
||||
{
|
||||
if (m_device == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the device is idle: everything goes at once
|
||||
m_deletions = nullptr;
|
||||
m_smokesimulate.destroy(m_device);
|
||||
m_smokeprepare.destroy(m_device);
|
||||
m_precipitationprepare.destroy(m_device);
|
||||
m_smokedraw.reset();
|
||||
m_precipitationdraw.reset();
|
||||
for (auto *buffer : {&m_particles, &m_sources, &m_owners, &m_smokeoutput, &m_smokecommand, &m_precipitationoutput, &m_precipitationcommand})
|
||||
{
|
||||
buffer->reset();
|
||||
}
|
||||
m_smokereadback.destroy();
|
||||
m_precipitationreadback.destroy();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::shared_ptr<buffer> gpu_particles::make_buffer(VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name)
|
||||
{
|
||||
auto result{std::make_shared<buffer>()};
|
||||
if (false == result->create(*m_memory, std::max<VkDeviceSize>(Size, 16), Usage, memory_kind::device, Name))
|
||||
{
|
||||
ErrorLog(std::string("particles: no buffer for the ") + Name);
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void gpu_particles::let_go(std::shared_ptr<buffer> &Buffer)
|
||||
{
|
||||
if ((Buffer != nullptr) && (m_deletions != nullptr))
|
||||
{
|
||||
// the frames in flight may still read it
|
||||
m_deletions->push(m_uploads->current_value(), [old = std::move(Buffer)]() {});
|
||||
}
|
||||
Buffer.reset();
|
||||
}
|
||||
|
||||
bool gpu_particles::gpu_init()
|
||||
{
|
||||
if ((m_device == VK_NULL_HANDLE) || (false == bool(m_host.compute)) || (false == bool(m_host.draw)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
auto kernel = [&](compute_kernel &Kernel, char const *Shader, char const *Name) {
|
||||
if (false == Kernel.create(m_device, *m_pipelines, m_shaders->get(Shader), m_passlayout, {}, Name))
|
||||
{
|
||||
throw gfx::shader_exception(std::string("no compute pipeline of ") + Name);
|
||||
}
|
||||
};
|
||||
kernel(m_smokesimulate, "particles_smoke_simulate.comp", "smoke simulation");
|
||||
kernel(m_smokeprepare, "particles_smoke_prepare.comp", "smoke");
|
||||
kernel(m_precipitationprepare, "particles_precipitation.comp", "precipitation");
|
||||
auto program = [&](char const *Vertex, char const *Fragment) {
|
||||
auto result{std::make_shared<scene_program>()};
|
||||
result->vertex = m_shaders->get(Vertex);
|
||||
result->fragment = m_shaders->get(Fragment);
|
||||
result->fragment_name = Fragment;
|
||||
result->vertex_name = Vertex;
|
||||
result->vertices = vertex_kind::particle_instance;
|
||||
result->name = std::string(Vertex) + " + " + Fragment;
|
||||
return result;
|
||||
};
|
||||
m_smokedraw = program("particles_smoke.vert", "particles_smoke.frag");
|
||||
m_precipitationdraw = program("particles_precipitation.vert", "particles_precipitation.frag");
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("particles: can't use the programs of the particles of the gpu, the smoke stays on the cpu: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
auto const commandusage{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
|
||||
m_smokecommand = make_buffer(4 * sizeof(std::uint32_t), commandusage, "command of the smoke");
|
||||
m_precipitationcommand = make_buffer(4 * sizeof(std::uint32_t), commandusage, "command of the precipitation");
|
||||
if ((m_smokecommand == nullptr) || (m_precipitationcommand == nullptr) || (false == m_smokereadback.create(*m_memory, 4 * sizeof(std::uint32_t), m_frames, "smoke read back")) ||
|
||||
(false == m_precipitationreadback.create(*m_memory, 4 * sizeof(std::uint32_t), m_frames, "precipitation read back")))
|
||||
{
|
||||
ErrorLog("particles: no buffers of the commands of the particles of the gpu, the smoke stays on the cpu");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_clear()
|
||||
{
|
||||
for (auto *buffer : {&m_particles, &m_sources, &m_owners, &m_smokeoutput, &m_precipitationoutput})
|
||||
{
|
||||
let_go(*buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources)
|
||||
{
|
||||
auto const commands{m_host.compute()};
|
||||
auto particles{make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(gl::particle_smoke), storage_usage, "smoke particles")};
|
||||
if (particles == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the particles of the places kept go over to the new storage, the new places are dead
|
||||
memory_barrier(commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
commands->fill_buffer(particles->handle(), 0, VK_WHOLE_SIZE, 0);
|
||||
if ((m_particles != nullptr) && (Kept > 0))
|
||||
{
|
||||
memory_barrier(commands, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
VkBufferCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
|
||||
region.size = std::min<VkDeviceSize>(static_cast<VkDeviceSize>(Kept) * sizeof(gl::particle_smoke), m_particles->size());
|
||||
VkCopyBufferInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
|
||||
copy.srcBuffer = m_particles->handle();
|
||||
copy.dstBuffer = particles->handle();
|
||||
copy.regionCount = 1;
|
||||
copy.pRegions = ®ion;
|
||||
commands->copy_buffer2(©);
|
||||
}
|
||||
memory_barrier(commands, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
let_go(m_particles);
|
||||
m_particles = std::move(particles);
|
||||
|
||||
let_go(m_owners);
|
||||
let_go(m_smokeoutput);
|
||||
let_go(m_sources);
|
||||
m_owners = make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(std::uint32_t), storage_usage, "smoke owners");
|
||||
m_smokeoutput = make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(gl::particle_draw), storage_usage | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "smoke draws");
|
||||
m_sources = make_buffer(static_cast<VkDeviceSize>(std::max<std::size_t>(Sources, 1)) * sizeof(gl::particle_smoke_source), storage_usage, "smoke sources");
|
||||
if (m_owners != nullptr)
|
||||
{
|
||||
// before the commands of the frame, the kernels of the frame read it
|
||||
m_uploads->upload_buffer(m_owners, Owners.data(), static_cast<VkDeviceSize>(Owners.size() * sizeof(std::uint32_t)));
|
||||
}
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records)
|
||||
{
|
||||
if ((m_particles == nullptr) || (m_sources == nullptr) || (m_owners == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (false == Records.empty())
|
||||
{
|
||||
m_uploads->upload_buffer(m_sources, Records.data(), static_cast<VkDeviceSize>(Records.size() * sizeof(gl::particle_smoke_source)));
|
||||
}
|
||||
auto const commands{m_host.compute()};
|
||||
simulate_constants constants{};
|
||||
constants.places = Simulation.places;
|
||||
constants.step = Simulation.step;
|
||||
constants.wind = Simulation.wind;
|
||||
constants.seed = Simulation.seed;
|
||||
m_host.bind(m_smokesimulate);
|
||||
m_smokesimulate.dispatch(commands,
|
||||
{buffer_descriptor(m_particles->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_sources->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_owners->handle(), 0, VK_WHOLE_SIZE)},
|
||||
constants, groups(Simulation.places));
|
||||
compute_barrier(commands);
|
||||
}
|
||||
|
||||
void gpu_particles::reset_command(command_stream *const Commands, buffer const &Command)
|
||||
{
|
||||
std::uint32_t const command[4]{4, 0, 0, 0};
|
||||
update_buffer(Commands, Command, command, sizeof(command), 0);
|
||||
}
|
||||
|
||||
void gpu_particles::collect(readback_ring &Readback, std::uint64_t const Completed, std::size_t &Drawn)
|
||||
{
|
||||
std::uint32_t command[4];
|
||||
if (Readback.pop(Completed, command, sizeof(command)))
|
||||
{
|
||||
Drawn = command[1];
|
||||
}
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn)
|
||||
{
|
||||
collect(m_smokereadback, m_host.completed(), Drawn);
|
||||
if ((m_particles == nullptr) || (m_sources == nullptr) || (m_owners == nullptr) || (m_smokeoutput == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const commands{m_host.compute()};
|
||||
reset_command(commands, *m_smokecommand);
|
||||
prepare_constants constants{};
|
||||
constants.places = Draw.places;
|
||||
constants.origin = Draw.origin;
|
||||
constants.view = Draw.view;
|
||||
constants.options = Draw.options;
|
||||
std::copy(Draw.planes.begin(), Draw.planes.end(), constants.planes);
|
||||
m_host.bind(m_smokeprepare);
|
||||
m_smokeprepare.dispatch(commands,
|
||||
{buffer_descriptor(m_particles->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_sources->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_owners->handle(), 0, VK_WHOLE_SIZE),
|
||||
buffer_descriptor(m_smokecommand->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_smokeoutput->handle(), 0, VK_WHOLE_SIZE)},
|
||||
constants, groups(Draw.places));
|
||||
compute_barrier(commands);
|
||||
// the count of the instances is what the draw takes, read back once the gpu is done with the frame
|
||||
m_smokereadback.push(commands, m_host.slot(), m_host.value(), m_smokecommand->handle(), 0, 4 * sizeof(std::uint32_t));
|
||||
m_host.draw(*m_smokedraw, m_smokeoutput->handle(), m_smokecommand->handle(), Draw.soft, glm::vec4{0.f});
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_collect_precipitation(std::size_t &Drawn)
|
||||
{
|
||||
collect(m_precipitationreadback, m_host.completed(), Drawn);
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_precipitation_storage(std::uint32_t const Capacity)
|
||||
{
|
||||
let_go(m_precipitationoutput);
|
||||
m_precipitationoutput = make_buffer(static_cast<VkDeviceSize>(Capacity) * sizeof(gl::particle_draw), storage_usage | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "precipitation draws");
|
||||
}
|
||||
|
||||
void gpu_particles::gpu_render_precipitation(precipitation_draw const &Draw)
|
||||
{
|
||||
if (m_precipitationoutput == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
precipitation_block block{};
|
||||
block.counts = Draw.counts;
|
||||
std::copy(Draw.offsets.begin(), Draw.offsets.end(), block.offsets);
|
||||
block.sizes = Draw.sizes;
|
||||
block.velocity = Draw.velocity;
|
||||
block.timing = Draw.timing;
|
||||
block.view = Draw.view;
|
||||
block.options = Draw.options;
|
||||
std::copy(Draw.planes.begin(), Draw.planes.end(), block.planes);
|
||||
auto const slice{m_ring->allocate(sizeof(block), m_alignment)};
|
||||
if (false == bool(slice))
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::memcpy(slice.data, &block, sizeof(block));
|
||||
auto const commands{m_host.compute()};
|
||||
reset_command(commands, *m_precipitationcommand);
|
||||
m_host.bind(m_precipitationprepare);
|
||||
m_precipitationprepare.run(commands,
|
||||
{buffer_descriptor(m_precipitationcommand->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_precipitationoutput->handle(), 0, VK_WHOLE_SIZE),
|
||||
buffer_descriptor(slice.buffer, slice.offset, sizeof(block))},
|
||||
groups(Draw.count));
|
||||
compute_barrier(commands);
|
||||
m_precipitationreadback.push(commands, m_host.slot(), m_host.value(), m_precipitationcommand->handle(), 0, 4 * sizeof(std::uint32_t));
|
||||
m_host.draw(*m_precipitationdraw, m_precipitationoutput->handle(), m_precipitationcommand->handle(), Draw.soft, Draw.pixel);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
105
rendering/vulkan14/vulkan14particles.h
Normal file
105
rendering/vulkan14/vulkan14particles.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 particles of the gpu in the vulkan renderer (the stage P6 of the plan, opengl46_gpu_particles): the storage buffers, the
|
||||
// compute kernels and the indirect draws of gfx::gpu_particles. the compute passes run between two parts of the rendering of
|
||||
// the colour pass (the host ends it and begins it again); the instances counted by a pass are read back once the gpu is done
|
||||
// with the frame, as the opengl renderer reads them a few frames later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/gpuparticles.h"
|
||||
#include "rendering/vulkan14/vulkan14compute.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
struct scene_program;
|
||||
|
||||
class gpu_particles : public gfx::gpu_particles
|
||||
{
|
||||
public:
|
||||
// what the renderer does for the particles
|
||||
struct host
|
||||
{
|
||||
// the rendering under way ended for the compute passes; the commands of the frame
|
||||
std::function<command_stream *()> compute;
|
||||
// the pass set of the pass bound for the kernel
|
||||
std::function<void(compute_kernel const &Kernel)> bind;
|
||||
// the rendering begun again and the draw of the instances recorded: Program with the blending of the scene, the instances
|
||||
// from Output, the command from Command, Data0 and Data1 in the push constants (data[0], data[1])
|
||||
std::function<void(scene_program &Program, VkBuffer Output, VkBuffer Command, glm::vec4 const &Data0, glm::vec4 const &Data1)> draw;
|
||||
// the slot of the frame in flight, the value of the timeline the gpu reaches when it's done with the frame, the one it reached
|
||||
std::function<std::uint32_t()> slot;
|
||||
std::function<std::uint64_t()> value;
|
||||
std::function<std::uint64_t()> completed;
|
||||
};
|
||||
|
||||
gpu_particles() = default;
|
||||
~gpu_particles() override;
|
||||
|
||||
// called once, before anything is made. Frames: the frames in flight
|
||||
void init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions, frame_ring &Ring,
|
||||
VkDeviceSize Alignment, std::uint32_t Frames, host Host);
|
||||
// everything goes (the device is idle)
|
||||
void destroy();
|
||||
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) override;
|
||||
void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) override;
|
||||
void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) override;
|
||||
void gpu_collect_precipitation(std::size_t &Drawn) override;
|
||||
void gpu_precipitation_storage(std::uint32_t const Capacity) override;
|
||||
void gpu_render_precipitation(precipitation_draw const &Draw) override;
|
||||
void gpu_clear() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<buffer> make_buffer(VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name);
|
||||
void let_go(std::shared_ptr<buffer> &Buffer);
|
||||
// the command of a draw reset before the compute pass counts its instances
|
||||
void reset_command(command_stream *Commands, buffer const &Command);
|
||||
static void collect(readback_ring &Readback, std::uint64_t Completed, std::size_t &Drawn);
|
||||
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
pipeline_library *m_pipelines{nullptr};
|
||||
shader_library *m_shaders{nullptr};
|
||||
VkDescriptorSetLayout m_passlayout{VK_NULL_HANDLE};
|
||||
allocator *m_memory{nullptr};
|
||||
uploader *m_uploads{nullptr};
|
||||
deletion_queue *m_deletions{nullptr};
|
||||
frame_ring *m_ring{nullptr};
|
||||
VkDeviceSize m_alignment{256};
|
||||
std::uint32_t m_frames{0};
|
||||
host m_host;
|
||||
|
||||
compute_kernel m_smokesimulate;
|
||||
compute_kernel m_smokeprepare;
|
||||
compute_kernel m_precipitationprepare;
|
||||
std::shared_ptr<scene_program> m_smokedraw;
|
||||
std::shared_ptr<scene_program> m_precipitationdraw;
|
||||
|
||||
// smoke
|
||||
std::shared_ptr<buffer> m_particles;
|
||||
std::shared_ptr<buffer> m_sources;
|
||||
std::shared_ptr<buffer> m_owners;
|
||||
std::shared_ptr<buffer> m_smokeoutput;
|
||||
std::shared_ptr<buffer> m_smokecommand;
|
||||
readback_ring m_smokereadback;
|
||||
// rain and snow
|
||||
std::shared_ptr<buffer> m_precipitationoutput;
|
||||
std::shared_ptr<buffer> m_precipitationcommand;
|
||||
readback_ring m_precipitationreadback;
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14passes.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
namespace vk14
|
||||
@@ -19,7 +20,7 @@ image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage
|
||||
return image_use{Image.handle(), Image.aspect(), &Image.state, Layout, Stage, Access};
|
||||
}
|
||||
|
||||
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
|
||||
void barriers(command_stream *Commands, std::vector<image_use> const &Uses)
|
||||
{
|
||||
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
|
||||
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
@@ -31,9 +32,23 @@ void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
|
||||
continue;
|
||||
}
|
||||
auto &state{*use.state};
|
||||
// reads after reads in the same layout need nothing
|
||||
// reads after reads in the same layout need nothing, unless they are in stages the barrier after the last write didn't
|
||||
// wait for: those wait for the stages which read the image already (which came after the write)
|
||||
if ((state.layout == use.layout) && ((state.access & writes) == 0) && ((use.access & writes) == 0))
|
||||
{
|
||||
if ((use.stage & ~state.stage) != 0)
|
||||
{
|
||||
VkMemoryBarrier2 chain{};
|
||||
chain.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
|
||||
chain.srcStageMask = (state.stage != VK_PIPELINE_STAGE_2_NONE ? state.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
|
||||
chain.dstStageMask = use.stage;
|
||||
chain.dstAccessMask = use.access;
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.memoryBarrierCount = 1;
|
||||
dependency.pMemoryBarriers = &chain;
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
state.stage |= use.stage;
|
||||
state.access |= use.access;
|
||||
continue;
|
||||
@@ -61,7 +76,7 @@ void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = static_cast<std::uint32_t>(list.size());
|
||||
dependency.pImageMemoryBarriers = list.data();
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
|
||||
pass_list::~pass_list()
|
||||
@@ -114,7 +129,7 @@ void pass_list::destroy()
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number)
|
||||
void pass_list::begin_frame(command_stream *Commands, std::uint32_t Frame, std::uint64_t Number)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_frame = Frame;
|
||||
@@ -124,7 +139,7 @@ void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::
|
||||
frame.used = 0;
|
||||
frame.number = Number;
|
||||
frame.recorded = true;
|
||||
vkCmdResetQueryPool(Commands, frame.pool, 0, max_passes * 2);
|
||||
Commands->reset_query_pool(frame.pool, 0, max_passes * 2);
|
||||
}
|
||||
|
||||
void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
|
||||
@@ -139,7 +154,7 @@ void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
|
||||
// (no timestamps on a queue without valid bits; the device is chosen with them, this is for safety)
|
||||
if ((frame.used < max_passes) && (m_mask != 0))
|
||||
{
|
||||
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
|
||||
m_commands->write_timestamp2(VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
|
||||
frame.names.emplace_back(Name);
|
||||
m_open = static_cast<int>(frame.used);
|
||||
++frame.used;
|
||||
@@ -158,7 +173,7 @@ void pass_list::end_pass()
|
||||
}
|
||||
if (m_open < static_cast<int>(max_passes))
|
||||
{
|
||||
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
|
||||
m_commands->write_timestamp2(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
|
||||
}
|
||||
end_label(m_commands);
|
||||
m_open = -1;
|
||||
@@ -167,7 +182,7 @@ void pass_list::end_pass()
|
||||
void pass_list::end_frame()
|
||||
{
|
||||
end_pass();
|
||||
m_commands = VK_NULL_HANDLE;
|
||||
m_commands = nullptr;
|
||||
}
|
||||
|
||||
void pass_list::collect(std::uint32_t Frame)
|
||||
|
||||
@@ -39,7 +39,7 @@ struct image_use
|
||||
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
|
||||
|
||||
// the barriers which bring the images from their states to the uses, the states updated
|
||||
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses);
|
||||
void barriers(command_stream *Commands, std::vector<image_use> const &Uses);
|
||||
|
||||
class pass_list
|
||||
{
|
||||
@@ -53,7 +53,7 @@ class pass_list
|
||||
void destroy();
|
||||
|
||||
// the frame Frame (index of the frame in flight) is recorded to Commands from now on; Number counts the frames
|
||||
void begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number);
|
||||
void begin_frame(command_stream *Commands, std::uint32_t Frame, std::uint64_t Number);
|
||||
// a pass: the barriers of its images, its label and the timestamp of its start
|
||||
void begin_pass(char const *Name, std::vector<image_use> const &Uses = {});
|
||||
void end_pass();
|
||||
@@ -85,7 +85,7 @@ class pass_list
|
||||
float m_period{1.f}; // ns per tick
|
||||
std::uint64_t m_mask{~0ull}; // the valid bits of the timestamps
|
||||
std::vector<frame_queries> m_frames;
|
||||
VkCommandBuffer m_commands{VK_NULL_HANDLE};
|
||||
command_stream *m_commands{nullptr};
|
||||
std::uint32_t m_frame{0};
|
||||
int m_open{-1};
|
||||
// averaged times of the parts, ms
|
||||
|
||||
@@ -37,6 +37,17 @@ float srgb_to_linear(float const Value)
|
||||
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
|
||||
}
|
||||
|
||||
// the threads of the renderer (gfx.threads), the calling one with them: 0, the cores less two
|
||||
std::size_t render_threads()
|
||||
{
|
||||
if (Global.gfx_threads > 0)
|
||||
{
|
||||
return static_cast<std::size_t>(Global.gfx_threads);
|
||||
}
|
||||
auto const cores{static_cast<std::size_t>(std::thread::hardware_concurrency())};
|
||||
return (cores > 3 ? cores - 2 : 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::string &Reason)
|
||||
@@ -69,25 +80,250 @@ bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::s
|
||||
Reason = "no command pool";
|
||||
return false;
|
||||
}
|
||||
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||
VkCommandBufferAllocateInfo bufferinfo{};
|
||||
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
bufferinfo.commandPool = frames[i].pool;
|
||||
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
bufferinfo.commandBufferCount = 2;
|
||||
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
|
||||
bufferinfo.commandBufferCount = 1;
|
||||
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].uploads)))
|
||||
{
|
||||
Reason = "no command buffer";
|
||||
return false;
|
||||
}
|
||||
frames[i].uploads = buffers[0];
|
||||
frames[i].commands = buffers[1];
|
||||
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
|
||||
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
|
||||
}
|
||||
if (Global.gfx_vulkan_renderthread)
|
||||
{
|
||||
start_render_thread();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::context::record(frame &Frame, bool const Uploads)
|
||||
{
|
||||
auto const handle{device.handle()};
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
Frame.upload_stream.finish();
|
||||
Frame.commands.finish();
|
||||
VkCommandBufferBeginInfo begin{};
|
||||
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
if (Uploads)
|
||||
{
|
||||
VK14_VERIFY(vkBeginCommandBuffer(Frame.uploads, &begin));
|
||||
Frame.upload_stream.replay(Frame.uploads);
|
||||
VK14_VERIFY(vkEndCommandBuffer(Frame.uploads));
|
||||
}
|
||||
// the chunks split evenly between the workers, each with a command pool of its own (a pool is used by one thread at a time);
|
||||
// which worker records a chunk doesn't matter: each chunk has a command buffer of its own, they go to the queue in their order
|
||||
auto const chunks{Frame.commands.chunks()};
|
||||
auto const threads{std::max<std::size_t>(1, std::min<std::size_t>(render_threads(), chunks))};
|
||||
while (Frame.workers.size() < threads)
|
||||
{
|
||||
VkCommandPoolCreateInfo poolinfo{};
|
||||
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
||||
poolinfo.queueFamilyIndex = device.families().graphics;
|
||||
VkCommandPool pool{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &pool)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Frame.workers.emplace_back().pool = pool;
|
||||
}
|
||||
Frame.chunks.assign(chunks, VK_NULL_HANDLE);
|
||||
workers.run(threads, threads, [&](std::size_t const Worker) {
|
||||
auto &commands{Frame.workers[Worker]};
|
||||
for (auto chunk = chunks * Worker / threads; chunk < chunks * (Worker + 1) / threads; ++chunk)
|
||||
{
|
||||
if (commands.used == commands.buffers.size())
|
||||
{
|
||||
VkCommandBufferAllocateInfo bufferinfo{};
|
||||
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
bufferinfo.commandPool = commands.pool;
|
||||
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
bufferinfo.commandBufferCount = 1;
|
||||
VkCommandBuffer buffer{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &buffer)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
commands.buffers.push_back(buffer);
|
||||
}
|
||||
auto const buffer{commands.buffers[commands.used++]};
|
||||
VK14_VERIFY(vkBeginCommandBuffer(buffer, &begin));
|
||||
Frame.commands.replay(chunk, buffer);
|
||||
VK14_VERIFY(vkEndCommandBuffer(buffer));
|
||||
Frame.chunks[chunk] = buffer;
|
||||
}
|
||||
});
|
||||
record_threads = threads;
|
||||
record_time = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
record_calls = Frame.commands.calls();
|
||||
record_chunks = chunks;
|
||||
return std::find(Frame.chunks.begin(), Frame.chunks.end(), VK_NULL_HANDLE) == Frame.chunks.end();
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::context::execute(render_job &Job, char const *&Failure)
|
||||
{
|
||||
auto &frame{*Job.target};
|
||||
if (false == record(frame, Job.uploaded))
|
||||
{
|
||||
Failure = "recording a frame";
|
||||
return false;
|
||||
}
|
||||
// the uploads first, in the same submit: their barriers make them done before the frame uses what they copied
|
||||
std::vector<VkCommandBufferSubmitInfo> commands;
|
||||
if (Job.uploaded)
|
||||
{
|
||||
VkCommandBufferSubmitInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||
info.commandBuffer = frame.uploads;
|
||||
commands.push_back(info);
|
||||
}
|
||||
// the chunks of the frame in their order: a rendering suspended at the end of one is resumed at the start of the next
|
||||
for (auto const chunk : frame.chunks)
|
||||
{
|
||||
VkCommandBufferSubmitInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||
info.commandBuffer = chunk;
|
||||
commands.push_back(info);
|
||||
}
|
||||
VkSubmitInfo2 submit{};
|
||||
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
|
||||
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(Job.waits.size());
|
||||
submit.pWaitSemaphoreInfos = Job.waits.data();
|
||||
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
|
||||
submit.pCommandBufferInfos = commands.data();
|
||||
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(Job.signals.size());
|
||||
submit.pSignalSemaphoreInfos = Job.signals.data();
|
||||
if (!VK14_VERIFY(vkQueueSubmit2(device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
|
||||
{
|
||||
Failure = "submitting a frame";
|
||||
return false;
|
||||
}
|
||||
if ((false == Job.windows.empty()) && (false == present(Job.windows)))
|
||||
{
|
||||
Failure = "presenting";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::context::present(std::vector<vk14::swapchain *> const &Windows)
|
||||
{
|
||||
std::vector<VkSemaphore> waits;
|
||||
std::vector<VkSwapchainKHR> swapchains;
|
||||
std::vector<std::uint32_t> images;
|
||||
std::vector<VkResult> results(Windows.size(), VK_SUCCESS);
|
||||
for (auto *window : Windows)
|
||||
{
|
||||
waits.push_back(window->present_semaphore());
|
||||
swapchains.push_back(window->handle());
|
||||
images.push_back(window->image_index());
|
||||
}
|
||||
VkPresentInfoKHR info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
info.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
|
||||
info.pWaitSemaphores = waits.data();
|
||||
info.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
|
||||
info.pSwapchains = swapchains.data();
|
||||
info.pImageIndices = images.data();
|
||||
info.pResults = results.data();
|
||||
auto const result{vkQueuePresentKHR(device.graphics_queue(), &info)};
|
||||
for (std::size_t i = 0; i < Windows.size(); ++i)
|
||||
{
|
||||
Windows[i]->presented(results[i]);
|
||||
}
|
||||
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
|
||||
{
|
||||
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::context::start_render_thread()
|
||||
{
|
||||
render_stop = false;
|
||||
render_thread = std::thread([this]() {
|
||||
std::unique_lock<std::mutex> lock(render_mutex);
|
||||
while (true)
|
||||
{
|
||||
render_wake.wait(lock, [this]() { return render_stop || render_queued; });
|
||||
if (false == render_queued)
|
||||
{
|
||||
// stopped, with nothing left to do
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
char const *failure{nullptr};
|
||||
auto done{false};
|
||||
try
|
||||
{
|
||||
done = execute(render_pending, failure);
|
||||
}
|
||||
catch (std::exception const &Error)
|
||||
{
|
||||
vk14::log_error(std::string("the render thread failed: ") + Error.what());
|
||||
failure = "recording a frame";
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
vk14::log_error("the render thread failed");
|
||||
failure = "recording a frame";
|
||||
}
|
||||
lock.lock();
|
||||
if ((false == done) && (render_failure == nullptr))
|
||||
{
|
||||
render_failure = (failure != nullptr ? failure : "recording a frame");
|
||||
}
|
||||
render_queued = false;
|
||||
render_idle.notify_all();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void vulkan14_renderer::context::stop_render_thread()
|
||||
{
|
||||
if (false == render_thread.joinable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_mutex);
|
||||
render_stop = true;
|
||||
}
|
||||
render_wake.notify_all();
|
||||
render_thread.join();
|
||||
}
|
||||
|
||||
void vulkan14_renderer::context::hand_over(render_job &&Job)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(render_mutex);
|
||||
render_idle.wait(lock, [this]() { return false == render_queued; });
|
||||
render_pending = std::move(Job);
|
||||
render_queued = true;
|
||||
}
|
||||
render_wake.notify_one();
|
||||
}
|
||||
|
||||
char const *vulkan14_renderer::context::drain()
|
||||
{
|
||||
if (false == render_threaded())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
std::unique_lock<std::mutex> lock(render_mutex);
|
||||
render_idle.wait(lock, [this]() { return false == render_queued; });
|
||||
render_wait = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
auto const failure{render_failure};
|
||||
render_failure = nullptr;
|
||||
return failure;
|
||||
}
|
||||
|
||||
// the parts of the renderer made once the device is there
|
||||
bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::string &Reason)
|
||||
{
|
||||
@@ -102,7 +338,7 @@ bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::st
|
||||
}
|
||||
auto const &limits{device.caps().limits};
|
||||
(void)limits;
|
||||
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring", ring_tail))
|
||||
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "frame ring", ring_tail))
|
||||
{
|
||||
Reason = "no ring for the data of the frames";
|
||||
return false;
|
||||
@@ -151,6 +387,8 @@ bool vulkan14_renderer::context::wait(std::uint64_t const Value) const
|
||||
void vulkan14_renderer::context::destroy()
|
||||
{
|
||||
auto const handle{device.handle()};
|
||||
// the frame given to the render thread goes to the queue first
|
||||
stop_render_thread();
|
||||
if (handle != VK_NULL_HANDLE)
|
||||
{
|
||||
// the end of the renderer, not a frame: everything on the gpu has to be done
|
||||
@@ -178,6 +416,10 @@ void vulkan14_renderer::context::destroy()
|
||||
{
|
||||
vkDestroyCommandPool(handle, frame.pool, nullptr);
|
||||
}
|
||||
for (auto &worker : frame.workers)
|
||||
{
|
||||
vkDestroyCommandPool(handle, worker.pool, nullptr);
|
||||
}
|
||||
}
|
||||
if (timeline != VK_NULL_HANDLE)
|
||||
{
|
||||
@@ -288,9 +530,11 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
|
||||
|
||||
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
|
||||
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
|
||||
vk14::log_info("not available with vulkan yet: the atmosphere, the sky ambient light, automatic exposure and bloom, stars (P1), the weather fog and clouds (P2), "
|
||||
"ambient occlusion and contact shadows (P3), shadows of lamps and headlights (P4), the culling on the gpu (P5), the heightmap terrain, grass and the particles "
|
||||
"of the gpu (P6), the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
|
||||
vk14::log_info("not available with vulkan yet: the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
|
||||
// the settings of eu07.ini which only the opengl renderer takes; everything else of gfx.* is taken here as there
|
||||
vk14::log_info("settings of the opengl renderer without effect with vulkan: compresstex (the textures go to the gpu as the files give them), gfx.depthprepass.replay (the scene "
|
||||
"is walked again for its depth), gfx.bufferstreaming, gfx.geometry.shared, gfx.format.color, gfx.format.depth, gfx.postfx.motionblur.format (the formats of the "
|
||||
"targets are fixed), gfx.gldebug (gfx.vulkan.validation instead)");
|
||||
WriteLog("Gfx Renderer: setup complete");
|
||||
update_info();
|
||||
return true;
|
||||
@@ -318,6 +562,12 @@ bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const
|
||||
return false;
|
||||
}
|
||||
auto &ctx{*m_context};
|
||||
// the windows the render thread presents are left as they are until it's done
|
||||
if (auto const *failure{ctx.drain()})
|
||||
{
|
||||
report_device_lost(failure);
|
||||
return false;
|
||||
}
|
||||
auto swapchain{std::make_unique<vk14::swapchain>()};
|
||||
std::string reason;
|
||||
if (false == swapchain->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
|
||||
@@ -343,7 +593,8 @@ void vulkan14_renderer::Shutdown()
|
||||
{
|
||||
if (m_context && (m_context->device.handle() != VK_NULL_HANDLE))
|
||||
{
|
||||
// the end of the renderer, not a frame: everything on the gpu has to be done
|
||||
// the end of the renderer, not a frame: everything on the gpu has to be done, the frame of the render thread with it
|
||||
m_context->stop_render_thread();
|
||||
vkDeviceWaitIdle(m_context->device.handle());
|
||||
}
|
||||
// the geometry on the gpu goes with the context; the data of the banks stays for the simulation
|
||||
@@ -389,34 +640,11 @@ void vulkan14_renderer::SwapBuffers()
|
||||
auto &ctx{*m_context};
|
||||
Timer::subsystem.gfx_swap.start();
|
||||
|
||||
// with the render thread the windows are presented by it (ctx.acquired was handed over to it)
|
||||
if (false == ctx.acquired.empty())
|
||||
{
|
||||
std::vector<VkSemaphore> waits;
|
||||
std::vector<VkSwapchainKHR> swapchains;
|
||||
std::vector<std::uint32_t> images;
|
||||
std::vector<VkResult> results(ctx.acquired.size(), VK_SUCCESS);
|
||||
for (auto *window : ctx.acquired)
|
||||
if (false == ctx.present(ctx.acquired))
|
||||
{
|
||||
waits.push_back(window->present_semaphore());
|
||||
swapchains.push_back(window->handle());
|
||||
images.push_back(window->image_index());
|
||||
}
|
||||
VkPresentInfoKHR present{};
|
||||
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
present.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
|
||||
present.pWaitSemaphores = waits.data();
|
||||
present.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
|
||||
present.pSwapchains = swapchains.data();
|
||||
present.pImageIndices = images.data();
|
||||
present.pResults = results.data();
|
||||
auto const result{vkQueuePresentKHR(ctx.device.graphics_queue(), &present)};
|
||||
for (std::size_t i = 0; i < ctx.acquired.size(); ++i)
|
||||
{
|
||||
ctx.acquired[i]->presented(results[i]);
|
||||
}
|
||||
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
|
||||
{
|
||||
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
|
||||
report_device_lost("presenting");
|
||||
}
|
||||
ctx.acquired.clear();
|
||||
@@ -484,6 +712,17 @@ void vulkan14_renderer::Update(double const Deltatime)
|
||||
{
|
||||
m_materials.on_weather_change();
|
||||
}
|
||||
if (((m_simulationstate.season != simulationstate.season) && (false == simulationstate.season.empty())) ||
|
||||
((m_simulationstate.weather != simulationstate.weather) && (false == simulationstate.weather.empty())))
|
||||
{
|
||||
// the heightmap terrain keeps copies of the textures of its layer materials
|
||||
if (m_context && m_context->scene.terrain)
|
||||
{
|
||||
m_context->scene.terrain->on_environment_change();
|
||||
}
|
||||
// the seasonal variants of the models are shown and hidden anew: the walks kept by the gpu culling go
|
||||
m_walkrecords.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
|
||||
@@ -511,7 +750,9 @@ void vulkan14_renderer::update_info()
|
||||
auto const &caps{ctx.device.caps()};
|
||||
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
|
||||
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, ui: " + to_string(Timer::subsystem.gfx_gui.average(), 2) +
|
||||
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "gpu frame parts:\n" + ctx.passes.info();
|
||||
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "command buffers recorded: " + to_string(ctx.record_time, 2) + " ms, " + std::to_string(ctx.record_calls) + " calls in " +
|
||||
std::to_string(ctx.record_chunks) + " chunks, " + std::to_string(ctx.record_threads) + (ctx.record_threads == 1 ? " thread" : " threads") +
|
||||
(ctx.render_threaded() ? ", on the render thread; waited for it: " + to_string(ctx.render_wait, 2) + " ms\n" : "\n") + "gpu frame parts:\n" + ctx.passes.info();
|
||||
VkDeviceSize usage{0}, budget{0};
|
||||
ctx.memory.budget(usage, budget);
|
||||
m_infostats = "device memory: " + vk14::memory_size_string(usage) + " of " + vk14::memory_size_string(budget) + (ctx.device.memory_budget_enabled() ? "" : " (no budget from the driver)") + "\n" + "images: " + std::to_string(ctx.images.size()) +
|
||||
@@ -522,6 +763,9 @@ void vulkan14_renderer::update_info()
|
||||
auto const extent{window->extent()};
|
||||
m_infostats += "window: " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + (window->srgb() ? ", srgb" : ", unorm") + (window->present_mode() == VK_PRESENT_MODE_FIFO_KHR ? ", vsync" : ", no vsync") + "\n";
|
||||
}
|
||||
// the statistics of the scene and of the cullings, as the opengl renderer gives them
|
||||
collect_cluster_stats();
|
||||
m_infostats += "\n" + scene_info() + cull_info();
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::Debug_Ui_State(std::optional<bool> State)
|
||||
|
||||
@@ -24,17 +24,27 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/lightarray.h"
|
||||
#include "rendering/common/scenewalk.h"
|
||||
#include "rendering/common/locallights.h"
|
||||
#include "rendering/common/skylight.h"
|
||||
#include "rendering/common/weatherstate.h"
|
||||
#include "rendering/common/shadowfit.h"
|
||||
#include "rendering/common/gpuparticles.h"
|
||||
#include "rendering/particles.h"
|
||||
#include "model/material.h"
|
||||
#include "vehicle/Camera.h"
|
||||
#include "gl/ubo.h"
|
||||
|
||||
class world_environment;
|
||||
struct VkRenderingInfo;
|
||||
namespace vk14
|
||||
{
|
||||
struct scene_program;
|
||||
struct target_formats;
|
||||
class image;
|
||||
class compute_kernel;
|
||||
struct draw_constants;
|
||||
enum class blend_mode;
|
||||
class instance_cull;
|
||||
struct image_use;
|
||||
} // namespace vk14
|
||||
|
||||
class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
@@ -69,6 +79,10 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
bool Blendable_Material(material_handle const Material) override;
|
||||
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
|
||||
IMaterial const *Material(material_handle const Material) const override;
|
||||
material_handle Terrain_Material(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr) override;
|
||||
void Terrain_Release(material_handle const Material) override;
|
||||
void Terrain_Grass(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks) override;
|
||||
// shaders
|
||||
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
|
||||
std::string Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const override;
|
||||
@@ -158,6 +172,89 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
|
||||
void walk_light_query(TSubModel *Submodel, bool const Begin) override;
|
||||
void walk_editor_cursor() override;
|
||||
gfx::geometrybank_manager &walk_geometry() override;
|
||||
bool walk_blending_enabled() const override;
|
||||
void walk_region_begin() override;
|
||||
bool walk_instances_begin() override;
|
||||
void walk_instances_end() override;
|
||||
|
||||
// the gpu culling of the instances (vulkan14indirect.cpp, the stage P5; opengl46_renderer::Render_Instances_Indirect() and the
|
||||
// rest): the cullings of the passes and the depth pyramid, made when the renderer starts
|
||||
bool init_cull();
|
||||
void destroy_cull();
|
||||
// the rendering of the pass under way, kept to be begun again where it was: it's ended for the compute passes of the culling
|
||||
// in the middle of the walk, and begun again with what it drew loaded, with the state of the draws as it was
|
||||
void remember_rendering(VkRenderingInfo const &Info, std::vector<vk14::image_use> const &Uses);
|
||||
void suspend_rendering();
|
||||
void resume_rendering();
|
||||
// the pass sets with the output of the culling as the instance buffer, made when the pass sets or the output changed; bound by
|
||||
// the draws until they're dropped (nullptr)
|
||||
void use_cull_pass_sets(vk14::instance_cull *Cull);
|
||||
// writes the pass sets (Cull null) or those of the culling
|
||||
void write_pass_sets(vk14::instance_cull *Cull);
|
||||
// draws of the instances made, and the calls which made them
|
||||
struct indirect_counts
|
||||
{
|
||||
std::uint32_t draws{0};
|
||||
std::uint32_t calls{0};
|
||||
indirect_counts &operator+=(indirect_counts const &Other)
|
||||
{
|
||||
draws += Other.draws;
|
||||
calls += Other.calls;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
using command_set = gfx::instance_cull::command_set;
|
||||
// the instances of the main view drawn from its culling: the depth pyramid, the culling, the walks, the depth and the colour,
|
||||
// in two phases with the occlusion test of the instances drawn first; inside the depth pass of the scene the depth alone,
|
||||
// the colour waits for the colour pass (render_instances_indirect_pending())
|
||||
void render_instances_indirect();
|
||||
// the same for the passes other than the main view: no occlusion test, no depth prepass, one phase
|
||||
void render_instances_indirect_pass();
|
||||
void render_instances_indirect_pending();
|
||||
// the walks of the culled groups (gfx::scene_walk::Walk_Indirect_Draws()), the draws put in order for the multi draws, and the
|
||||
// commands filled in by the culling
|
||||
void record_indirect_draws(vk14::instance_cull &Cull, bool const Cascade);
|
||||
// the recorded draws with the commands of the set: their colour with the programs of their materials, their depth with the
|
||||
// programs of the depth prepass, the shadows with the programs of the shadows
|
||||
indirect_counts draw_indirect_colour(vk14::instance_cull &Cull, command_set const Set);
|
||||
indirect_counts draw_indirect_depth(vk14::instance_cull &Cull, command_set const Set);
|
||||
indirect_counts draw_indirect_shadow(vk14::instance_cull &Cull, command_set const Set);
|
||||
// Count recorded draws from First with the program (a multi draw with more than one) and the state of the draws
|
||||
bool draw_indirect(vk14::instance_cull &Cull, command_set const Set, std::size_t const First, std::uint32_t const Count, vk14::scene_program &Program, vk14::blend_mode const Blend);
|
||||
// the variant of a program for the multi draws, nullptr when there's none
|
||||
vk14::scene_program *multidraw_program(vk14::scene_program &Program);
|
||||
vk14::instance_cull &pass_cull();
|
||||
// the shadow maps of the lamps (vulkan14lamps.cpp, the stage P4; opengl46_renderer::Render_local_shadows() and the rest): the cab
|
||||
// lamp, the scenery lamps, one of them, the headlights, one set of them; the culling of a map (nullptr when its instances go
|
||||
// through the cpu); the rendering of the map set up by the maps; the maps put in the layout the shading reads them in
|
||||
void render_local_shadows();
|
||||
void render_lamp_shadows();
|
||||
void render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab);
|
||||
void render_headlight_shadows();
|
||||
void render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab);
|
||||
vk14::instance_cull *light_shadow_cull(std::vector<std::unique_ptr<vk14::instance_cull>> &Culls, std::size_t const Index, bool const Enabled);
|
||||
void begin_local_shadow_rendering();
|
||||
void end_local_shadow_rendering();
|
||||
void ready_lamp_maps();
|
||||
bool m_cubeshadows{false}; // the shadow pass draws to a cube map, with the programs of the cube maps
|
||||
vk14::instance_cull *m_lightshadowcull{nullptr}; // the culling of the map of headlights or of a lamp being drawn
|
||||
std::size_t m_lampscenerydrawn{0}; // scenery parts of the maps of lamps drawn in this frame so far
|
||||
unsigned int m_instancecullframe{~0u}; // the frame the culling of the main view ran in last
|
||||
int m_shadowcascade{-1}; // the cascade of the sun shadows being drawn
|
||||
bool m_indirectmultidraw{false}; // the recorded draws are in order for the multi draws
|
||||
vk14::instance_cull *m_indirectcull{nullptr}; // the culling the recorded draws were made for
|
||||
command_set m_indirectcolourset{command_set::first}; // the commands the colour pass draws the pending instances with
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm): the cascades of the frame fitted to the
|
||||
// latest measurement (opengl46_renderer::Shadow_Fit()), the measurement of the depth of the finished view, the state for the panel,
|
||||
// the area of the cascade being set up and the sizes setup_pass() found for it
|
||||
bool shadow_fit(viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits, gfx::shadow_fit::areas &Areas);
|
||||
char const *shadow_fit_unavailable(viewport_config const &Viewport) const;
|
||||
void measure_shadow_fit(std::size_t const Viewport);
|
||||
gfx::shadow_fit::fit_state m_shadowfitstate;
|
||||
std::array<glm::vec3, 8> const *m_shadowfitarea{nullptr};
|
||||
float m_shadowslicesize{0.f};
|
||||
float m_shadowdrawnsize{0.f};
|
||||
|
||||
// the scene (vulkan14scene.cpp)
|
||||
bool init_scene(std::string &Reason);
|
||||
@@ -196,6 +293,14 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
void setup_shadow_matrices();
|
||||
// the light clusters of the pass: the grid and the lists of the lights
|
||||
void update_clusters(glm::ivec2 const &Targetsize);
|
||||
// which program of the depth prepass stands for the program of the material: 0 none, 1 without the alpha test, 2 with it
|
||||
int prepass_class(material_handle const Material);
|
||||
// the depth of the whole opaque scene of the colour pass of the main viewport before its colour, for the contact shadows
|
||||
// (Contactonly: with them alone) or for itself (gfx.depthprepass.scene), and the contact shadows set up to read it
|
||||
void render_scene_depth(std::size_t const Viewport, glm::mat4 const &Future);
|
||||
bool contact_shadows_wanted(viewport_config const &Viewport) const;
|
||||
void setup_contact_shadows(std::size_t const Viewport);
|
||||
bool m_contactshadows{false};
|
||||
// the materials
|
||||
material_handle invalid_material() const
|
||||
{
|
||||
@@ -215,6 +320,44 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
// the screenshot copied with a frame the gpu is done with, saved
|
||||
void update_screenshot();
|
||||
void bind_pass_set();
|
||||
void update_pass_sets();
|
||||
// the pass set of the colour of the main view bound for the compute kernels, before the dispatches of Kernel (the kernels share
|
||||
// the layout of set 0 and the push constants, so the set stays bound for the ones which follow)
|
||||
void bind_compute_set(vk14::compute_kernel const &Kernel);
|
||||
// the light of the sky (vulkan14sky.cpp): the resources, their first state, the light of the frame worked out before its
|
||||
// passes, the environment map prefiltered with the GGX lobe, the stars of the sky pass
|
||||
bool init_sky(std::string &Reason);
|
||||
void destroy_sky();
|
||||
void clear_sky_images();
|
||||
void update_sky();
|
||||
void prefilter_environment();
|
||||
void render_stars(glm::mat4 const &View);
|
||||
// the automatic exposure of the image of the main view, and its bloom chain; the result of the chain, null without it
|
||||
void measure_exposure(vk14::image &Image);
|
||||
vk14::image *render_bloom(std::size_t const Viewport, vk14::image &Image);
|
||||
// the weather (vulkan14weather.cpp): the resources, the noises of the clouds, the block of the pass, what the weather is for the
|
||||
// frame (before its passes), the clouds and the fog volume of the colour pass of the main view, the clouds and the fog over the sky
|
||||
bool init_weather(std::string &Reason);
|
||||
void destroy_weather();
|
||||
void make_cloud_noise();
|
||||
void upload_weather_block();
|
||||
void update_weather();
|
||||
void render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp);
|
||||
void render_sky_weather(glm::mat4 const &Projection, bool const Mainview);
|
||||
// the ambient occlusion (vulkan14ao.cpp): the resources, and the occlusion of a viewport multiplied into its colour
|
||||
bool init_ao(std::string &Reason);
|
||||
void destroy_ao();
|
||||
void render_ao(std::size_t const Viewport, int const Mode);
|
||||
// the particles of the gpu: what the colour pass under way draws them with, and the depth of the scene drawn so far copied for
|
||||
// the soft particles (false: they aren't soft)
|
||||
gfx::gpu_particles::view particles_view() const;
|
||||
bool setup_particles_depth(std::size_t const Viewport);
|
||||
bool m_particlesoft{false};
|
||||
// the grass of the heightmap terrain seen from the main view, over the opaque scene (opengl46_renderer::Render_grass())
|
||||
void render_grass(glm::mat4 const &Future);
|
||||
// a pass of the post-processing: the fragment shader drawn over the whole of the target, reading the images (Pass: a pass of
|
||||
// its own, for the gpu times). Repeated: a bit for each input read texel by texel and repeated (the noise of the ssao)
|
||||
void post_pass(char const *Fragment, vk14::image &Target, std::vector<vk14::image *> const &Inputs, vk14::draw_constants const &Constants, char const *Pass = nullptr, unsigned const Repeated = 0);
|
||||
bool bind_scene_pipeline(vk14::scene_program &Program, vk14::target_formats const &Targets, vk14::blend_mode const Blend, int const Topology);
|
||||
void apply_draw_state();
|
||||
void push_textures();
|
||||
@@ -223,6 +366,11 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
void update_texture_release();
|
||||
|
||||
std::unique_ptr<context> m_context;
|
||||
// the light of the sky and the weather of the frame, shared with the opengl renderer
|
||||
gfx::sky_light m_skylight;
|
||||
gfx::weather_state m_weatherstate;
|
||||
// the environment map prefiltered with the GGX lobe is bound for the pass (light_data.env_ggx)
|
||||
bool m_envmap_bound{false};
|
||||
// the data of the geometry, which the simulation reads back, and the banks with it on the gpu
|
||||
gfx::geometrybank_manager m_geometry;
|
||||
std::vector<gfx::geometry_bank *> m_banks;
|
||||
@@ -295,6 +443,24 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
|
||||
debug_stats m_laststats;
|
||||
|
||||
void update_info();
|
||||
// the debug windows (vulkan14debug.cpp, opengl46_renderer::draw_debug_ui()), the lines of the cullings and of the scene for them
|
||||
// and for the statistics, the counters of the light clusters read back, and the heatmap of the clusters over the output of a
|
||||
// viewport (opengl46_renderer::Render_cluster_heatmap())
|
||||
void draw_debug_ui();
|
||||
std::string cull_info();
|
||||
std::string scene_info();
|
||||
void collect_cluster_stats();
|
||||
void render_cluster_heatmap(std::size_t const Viewport);
|
||||
// the headlights (opengl46_renderer::headlight_config_s), set by the debug window
|
||||
struct headlight_config
|
||||
{
|
||||
float in_cutoff{1.005f};
|
||||
float out_cutoff{0.993f};
|
||||
float falloff_linear{0.15f};
|
||||
float falloff_quadratic{0.15f};
|
||||
float intensity{1.0f};
|
||||
float ambient{0.0f};
|
||||
} m_headlightconfig;
|
||||
// the device was lost: what was on the gpu goes to the log
|
||||
void report_device_lost(char const *Where);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14shaders.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "rendering/vulkan14/vulkan14bindings.h"
|
||||
#include "rendering/common/localshadows.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
@@ -439,6 +441,7 @@ std::uint64_t pipeline_library::key_of(pipeline_desc const &Desc) const
|
||||
}
|
||||
hash_value(hash, Desc.depth_format);
|
||||
hash_value(hash, Desc.samples);
|
||||
hash_value(hash, Desc.view_mask);
|
||||
for (auto const value : Desc.specialization)
|
||||
{
|
||||
hash_value(hash, value);
|
||||
@@ -546,6 +549,9 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
|
||||
{
|
||||
case blend_mode::none:
|
||||
break;
|
||||
case blend_mode::no_colour:
|
||||
attachment.colorWriteMask = 0;
|
||||
break;
|
||||
case blend_mode::alpha:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
@@ -582,6 +588,24 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
case blend_mode::under_transmittance:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
case blend_mode::multiply:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR;
|
||||
attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
case blend_mode::additive:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
@@ -609,6 +633,7 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
|
||||
rendering.colorAttachmentCount = static_cast<std::uint32_t>(Desc.colour_formats.size());
|
||||
rendering.pColorAttachmentFormats = Desc.colour_formats.data();
|
||||
rendering.depthAttachmentFormat = Desc.depth_format;
|
||||
rendering.viewMask = Desc.view_mask;
|
||||
|
||||
VkGraphicsPipelineCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
@@ -703,23 +728,23 @@ std::string pipeline_library::info() const
|
||||
return "pipelines: " + std::to_string(m_pipelines.size()) + " (made in " + buffer + " ms)";
|
||||
}
|
||||
|
||||
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
|
||||
void set_draw_state(command_stream *Commands, VkExtent2D Extent)
|
||||
{
|
||||
VkViewport viewport{0.f, 0.f, static_cast<float>(Extent.width), static_cast<float>(Extent.height), 0.f, 1.f};
|
||||
vkCmdSetViewport(Commands, 0, 1, &viewport);
|
||||
Commands->set_viewport(0, 1, &viewport);
|
||||
VkRect2D scissor{{0, 0}, Extent};
|
||||
vkCmdSetScissor(Commands, 0, 1, &scissor);
|
||||
vkCmdSetCullMode(Commands, VK_CULL_MODE_NONE);
|
||||
vkCmdSetFrontFace(Commands, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
vkCmdSetPrimitiveTopology(Commands, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
vkCmdSetDepthTestEnable(Commands, VK_FALSE);
|
||||
vkCmdSetDepthWriteEnable(Commands, VK_FALSE);
|
||||
Commands->set_scissor(0, 1, &scissor);
|
||||
Commands->set_cull_mode(VK_CULL_MODE_NONE);
|
||||
Commands->set_front_face(VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
Commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
Commands->set_depth_test_enable(VK_FALSE);
|
||||
Commands->set_depth_write_enable(VK_FALSE);
|
||||
// reverse z, as the opengl renderer
|
||||
vkCmdSetDepthCompareOp(Commands, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
vkCmdSetDepthBiasEnable(Commands, VK_FALSE);
|
||||
vkCmdSetDepthBias(Commands, 0.f, 0.f, 0.f);
|
||||
vkCmdSetPrimitiveRestartEnable(Commands, VK_FALSE);
|
||||
vkCmdSetStencilTestEnable(Commands, VK_FALSE);
|
||||
Commands->set_depth_compare_op(VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
Commands->set_depth_bias_enable(VK_FALSE);
|
||||
Commands->set_depth_bias(0.f, 0.f, 0.f);
|
||||
Commands->set_primitive_restart_enable(VK_FALSE);
|
||||
Commands->set_stencil_test_enable(VK_FALSE);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
@@ -746,6 +771,21 @@ std::string binding_preamble()
|
||||
define("SHADOW_TEX", SHADOW_TEX);
|
||||
define("ENV_TEX", ENV_TEX);
|
||||
define("HEADLIGHT_TEX", HEADLIGHT_TEX);
|
||||
define("SKY_SSBO", SKY_SSBO);
|
||||
define("ATMOSPHERE_UBO", ATMOSPHERE_UBO);
|
||||
define("SKY_VIEW_TEX", SKY_VIEW_TEX);
|
||||
define("ENV_GGX_TEX", ENV_GGX_TEX);
|
||||
define("WEATHER_UBO", WEATHER_UBO);
|
||||
define("WEATHER_HORIZON_UBO", WEATHER_HORIZON_UBO);
|
||||
define("FOG_VOLUME_TEX", FOG_VOLUME_TEX);
|
||||
define("CLOUD_SHADOW_TEX", CLOUD_SHADOW_TEX);
|
||||
define("CLOUD_SHAPE_TEX", CLOUD_SHAPE_TEX);
|
||||
define("CLOUD_DETAIL_TEX", CLOUD_DETAIL_TEX);
|
||||
define("CLOUD_COVERAGE_TEX", CLOUD_COVERAGE_TEX);
|
||||
define("LOCAL_SHADOW_TEX", LOCAL_SHADOW_TEX);
|
||||
define("LOCAL_SHADOW_LAMPS_TEX", LOCAL_SHADOW_LAMPS_TEX);
|
||||
define("HEADLIGHT_SHADOW_TEX", HEADLIGHT_SHADOW_TEX);
|
||||
define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX);
|
||||
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
|
||||
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
|
||||
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
|
||||
@@ -756,6 +796,9 @@ std::string binding_preamble()
|
||||
define("CL_LIGHT_SSBO", CL_LIGHT_SSBO);
|
||||
define("POST_SET", POST_SET);
|
||||
define("POST_TEXTURE_FIRST", POST_TEXTURE_FIRST);
|
||||
define("POST_TEXTURES", POST_TEXTURES);
|
||||
define("POST_EXPOSURE_SSBO", POST_EXPOSURE_SSBO);
|
||||
define("COMPUTE_SET", COMPUTE_SET);
|
||||
define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE);
|
||||
define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA);
|
||||
define("SPEC_SHADOWMAP_ENABLED", SPEC_SHADOWMAP_ENABLED);
|
||||
@@ -776,6 +819,31 @@ std::string binding_preamble()
|
||||
define("MAX_LIGHTS_PER_CLUSTER", gl::MAX_LIGHTS_PER_CLUSTER);
|
||||
define("CLUSTER_BUILD_GROUP_SIZE", gl::CLUSTER_BUILD_GROUP_SIZE);
|
||||
define("CLUSTER_CULL_GROUP_SIZE", gl::CLUSTER_CULL_GROUP_SIZE);
|
||||
// the sizes of the stages P: the light of the sky, the exposure, the weather, the culling on the gpu, the shadow fit, the particles
|
||||
define("ENVMAP_GGX_LEVELS", static_cast<unsigned int>(gl::ENVMAP_GGX_LEVELS), "U");
|
||||
preamble += "#define ENVMAP_GGX_ROUGHNESS_MIN " + std::to_string(gl::ENVMAP_GGX_ROUGHNESS_MIN) + "\n";
|
||||
define("ENVMAP_GGX_GROUP_SIZE", static_cast<unsigned int>(gl::ENVMAP_GGX_GROUP_SIZE));
|
||||
define("EXPOSURE_HISTOGRAM_BINS", gl::EXPOSURE_HISTOGRAM_BINS);
|
||||
define("EXPOSURE_HISTOGRAM_GROUP_SIZE", gl::EXPOSURE_HISTOGRAM_GROUP_SIZE);
|
||||
define("ATMOSPHERE_TRANSMITTANCE_WIDTH", gl::ATMOSPHERE_TRANSMITTANCE_WIDTH);
|
||||
define("ATMOSPHERE_TRANSMITTANCE_HEIGHT", gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT);
|
||||
define("ATMOSPHERE_MULTISCATTERING_SIZE", gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
define("ATMOSPHERE_SKYVIEW_WIDTH", gl::ATMOSPHERE_SKYVIEW_WIDTH);
|
||||
define("ATMOSPHERE_SKYVIEW_HEIGHT", gl::ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
define("ATMOSPHERE_AMBIENT_GROUP_SIZE", gl::ATMOSPHERE_AMBIENT_GROUP_SIZE);
|
||||
define("MAX_CLOUD_LAYERS", static_cast<unsigned int>(gl::MAX_CLOUD_LAYERS));
|
||||
define("FOG_HORIZON_SAMPLES", static_cast<unsigned int>(gl::FOG_HORIZON_SAMPLES));
|
||||
define("WEATHER_GROUP_SIZE", gl::WEATHER_GROUP_SIZE);
|
||||
preamble += "#define FOG_VOLUME_BIAS " + std::to_string(gl::FOG_VOLUME_BIAS) + "\n";
|
||||
define("INSTANCE_CULL_GROUP_SIZE", gl::INSTANCE_CULL_GROUP_SIZE);
|
||||
define("INSTANCE_OFFSETS_GROUP_SIZE", gl::INSTANCE_OFFSETS_GROUP_SIZE);
|
||||
define("INSTANCE_COMMAND_GROUP_SIZE", gl::INSTANCE_COMMAND_GROUP_SIZE);
|
||||
define("INSTANCE_COUNTER_HEADER", gl::INSTANCE_COUNTER_HEADER);
|
||||
define("INSTANCE_JOB_SLEEPERS", static_cast<unsigned int>(gl::INSTANCE_JOB_SLEEPERS), "U");
|
||||
define("HIZ_GROUP_SIZE", gl::HIZ_GROUP_SIZE);
|
||||
define("SHADOW_FIT_GROUP_SIZE", gl::SHADOW_FIT_GROUP_SIZE);
|
||||
preamble += "#define LOCAL_SHADOW_NEAR " + std::to_string(gfx::local_shadows::shadow_near) + "\n";
|
||||
define("PARTICLE_GROUP_SIZE", gl::PARTICLE_GROUP_SIZE);
|
||||
return preamble;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,10 @@ enum class blend_mode
|
||||
additive,
|
||||
// the blending of the scene, as glBlendFunc() of the opengl renderer gives it to the colour and the alpha alike
|
||||
scene_alpha, // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
|
||||
scene_additive // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
|
||||
scene_additive, // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
|
||||
under_transmittance, // GL_ONE, GL_SRC_ALPHA: the colour added, what is there kept by the alpha (the clouds and the fog over the sky)
|
||||
multiply, // GL_ZERO, GL_SRC_COLOR: what is there times the colour (the ambient occlusion)
|
||||
no_colour // the colour isn't written, only the depth (the depth prepass of the instances inside the colour pass)
|
||||
};
|
||||
|
||||
struct vertex_layout
|
||||
@@ -100,6 +103,8 @@ struct pipeline_desc
|
||||
std::vector<VkFormat> colour_formats;
|
||||
VkFormat depth_format{VK_FORMAT_UNDEFINED};
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
// the views drawn at once (multiview, the cube shadow maps of the lamps), 0: none
|
||||
std::uint32_t view_mask{0};
|
||||
// the values of the specialization constants 0, 1, 2 ... of all the stages
|
||||
std::vector<std::uint32_t> specialization;
|
||||
std::string name;
|
||||
@@ -132,6 +137,6 @@ class pipeline_library
|
||||
};
|
||||
|
||||
// the dynamic state of a pipeline in its plain form: the whole of Extent, no culling, no depth, triangles
|
||||
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent);
|
||||
void set_draw_state(command_stream *Commands, VkExtent2D Extent);
|
||||
|
||||
} // namespace vk14
|
||||
|
||||
167
rendering/vulkan14/vulkan14shadows.cpp
Normal file
167
rendering/vulkan14/vulkan14shadows.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14shadows.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
local_shadow_maps::~local_shadow_maps()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void local_shadow_maps::init(VkDevice const Device, allocator &Memory, deletion_queue &Deletions)
|
||||
{
|
||||
m_device = Device;
|
||||
m_memory = &Memory;
|
||||
m_deletions = &Deletions;
|
||||
}
|
||||
|
||||
void local_shadow_maps::destroy()
|
||||
{
|
||||
// the device is idle: the maps go at once
|
||||
m_deletions = nullptr;
|
||||
let_go(m_cab, m_cabviews);
|
||||
let_go(m_lamps, m_lampviews);
|
||||
let_go(m_headlights, m_headlightviews);
|
||||
}
|
||||
|
||||
bool local_shadow_maps::make(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views, int const Size, int const Layers, bool const Cube, char const *Name)
|
||||
{
|
||||
if ((m_memory == nullptr) || (Size <= 0) || (Layers <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
image_desc desc;
|
||||
desc.format = VK_FORMAT_D32_SFLOAT;
|
||||
desc.extent = {static_cast<std::uint32_t>(Size), static_cast<std::uint32_t>(Size), 1};
|
||||
desc.layers = static_cast<std::uint32_t>(Layers);
|
||||
desc.view_type = (Cube ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_2D_ARRAY);
|
||||
desc.flags = (Cube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0);
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
Map = std::make_shared<image>();
|
||||
if (false == Map->create(*m_memory, desc, Name))
|
||||
{
|
||||
Map.reset();
|
||||
return false;
|
||||
}
|
||||
// the cubes drawn with multiview, six layers each, or the layers drawn one by one
|
||||
auto const step{Cube ? 6 : 1};
|
||||
for (int first = 0; first < Layers; first += step)
|
||||
{
|
||||
VkImageViewCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
info.image = Map->handle();
|
||||
info.viewType = (Cube ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D);
|
||||
info.format = desc.format;
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, static_cast<std::uint32_t>(first), static_cast<std::uint32_t>(step)};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
if (vkCreateImageView(m_device, &info, nullptr, &view) != VK_SUCCESS)
|
||||
{
|
||||
let_go(Map, Views);
|
||||
return false;
|
||||
}
|
||||
Views.push_back(view);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void local_shadow_maps::let_go(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views)
|
||||
{
|
||||
if (m_deletions != nullptr)
|
||||
{
|
||||
// the frames in flight may still read them
|
||||
m_deletions->push(m_value, [device = m_device, views = std::move(Views), map = std::move(Map)]() {
|
||||
for (auto *view : views)
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto *view : Views)
|
||||
{
|
||||
vkDestroyImageView(m_device, view, nullptr);
|
||||
}
|
||||
}
|
||||
Views.clear();
|
||||
Map.reset();
|
||||
}
|
||||
|
||||
bool local_shadow_maps::gpu_allocate(int const Size)
|
||||
{
|
||||
return make(m_cab, m_cabviews, Size, 6 * (cab_slot + 1), true, "lamp shadow map");
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_release()
|
||||
{
|
||||
let_go(m_cab, m_cabviews);
|
||||
}
|
||||
|
||||
bool local_shadow_maps::gpu_allocate_lamps(int const Count, int const Size)
|
||||
{
|
||||
// the scenery parts and the working copies
|
||||
return make(m_lamps, m_lampviews, Size, 6 * 2 * Count, true, "scenery lamp shadow maps");
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_release_lamps()
|
||||
{
|
||||
let_go(m_lamps, m_lampviews);
|
||||
}
|
||||
|
||||
bool local_shadow_maps::gpu_allocate_headlights(int const Count, int const Size)
|
||||
{
|
||||
return make(m_headlights, m_headlightviews, Size, Count, false, "headlight shadow maps");
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_release_headlights()
|
||||
{
|
||||
let_go(m_headlights, m_headlightviews);
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_begin_cab()
|
||||
{
|
||||
m_target = draw_target{m_cab.get(), m_cabviews.empty() ? VK_NULL_HANDLE : m_cabviews[cab_slot], static_cast<std::uint32_t>(m_size), 0x3f, true, true};
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles)
|
||||
{
|
||||
auto const cube{Vehicles ? Workingcube : Scenerycube};
|
||||
m_target = draw_target{m_lamps.get(), (static_cast<std::size_t>(cube) < m_lampviews.size()) ? m_lampviews[cube] : VK_NULL_HANDLE, static_cast<std::uint32_t>(m_lampssize), 0x3f, (false == Vehicles), true};
|
||||
if (Vehicles && m_lamps && (m_commands != nullptr))
|
||||
{
|
||||
// the vehicles go over the scenery: the copy of the scenery part
|
||||
transition(m_commands, *m_lamps, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
VkImageCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2;
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, static_cast<std::uint32_t>(Scenerycube * 6), 6};
|
||||
region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, static_cast<std::uint32_t>(Workingcube * 6), 6};
|
||||
region.extent = {static_cast<std::uint32_t>(m_lampssize), static_cast<std::uint32_t>(m_lampssize), 1};
|
||||
VkCopyImageInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
|
||||
copy.srcImage = m_lamps->handle();
|
||||
copy.srcImageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
copy.dstImage = m_lamps->handle();
|
||||
copy.dstImageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
copy.regionCount = 1;
|
||||
copy.pRegions = ®ion;
|
||||
m_commands->copy_image2(©);
|
||||
}
|
||||
}
|
||||
|
||||
void local_shadow_maps::gpu_begin_headlight(std::size_t const Slot)
|
||||
{
|
||||
m_target = draw_target{m_headlights.get(), (Slot < m_headlightviews.size()) ? m_headlightviews[Slot] : VK_NULL_HANDLE, static_cast<std::uint32_t>(m_headlightssize), 0, true, false};
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
113
rendering/vulkan14/vulkan14shadows.h
Normal file
113
rendering/vulkan14/vulkan14shadows.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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_local_shadows): the maps of gfx::local_shadows,
|
||||
// which tells what they hold. the six faces of a cube map are drawn at once by multiview (shadowcube.vert reads the face from
|
||||
// gl_ViewIndex) instead of the geometry shader of the opengl renderer. the renderer draws to the target the class sets up
|
||||
// (target()), the copies and the clears of the cubes of the lamps are recorded into the commands of the frame
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/localshadows.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class local_shadow_maps : public gfx::local_shadows
|
||||
{
|
||||
public:
|
||||
// what the drawing begun last draws to
|
||||
struct draw_target
|
||||
{
|
||||
image *map{nullptr};
|
||||
VkImageView view{VK_NULL_HANDLE}; // the layers of the cube (6, multiview) or of the headlights (1)
|
||||
std::uint32_t size{0};
|
||||
std::uint32_t view_mask{0}; // 0x3f for a cube
|
||||
bool clear{true}; // the cube or the layer starts cleared, the working copy of a lamp from the copy of its scenery part
|
||||
bool cube{true};
|
||||
};
|
||||
|
||||
local_shadow_maps() = default;
|
||||
~local_shadow_maps() override;
|
||||
|
||||
// Device, Memory, Deletions: what the maps are made and let go with; called once
|
||||
void init(VkDevice Device, allocator &Memory, deletion_queue &Deletions);
|
||||
// the maps go (the device is about to)
|
||||
void destroy();
|
||||
// the commands of the frame being recorded and the value of the timeline the gpu reaches when it's done with it, before the drawing
|
||||
void begin_frame(command_stream *Commands, std::uint64_t Value)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_value = Value;
|
||||
}
|
||||
// the frame was recorded: no commands go to it any more
|
||||
void end_frame()
|
||||
{
|
||||
m_commands = nullptr;
|
||||
}
|
||||
draw_target const &target() const
|
||||
{
|
||||
return m_target;
|
||||
}
|
||||
// the maps, for the pass sets (null: none, the stand-ins are read)
|
||||
image *cab_map() const
|
||||
{
|
||||
return m_cab.get();
|
||||
}
|
||||
image *lamp_maps() const
|
||||
{
|
||||
return m_lamps.get();
|
||||
}
|
||||
image *headlight_maps() const
|
||||
{
|
||||
return m_headlights.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool gpu_allocate(int const Size) override;
|
||||
void gpu_release() override;
|
||||
bool gpu_allocate_lamps(int const Count, int const Size) override;
|
||||
void gpu_release_lamps() override;
|
||||
bool gpu_allocate_headlights(int const Count, int const Size) override;
|
||||
void gpu_release_headlights() override;
|
||||
void gpu_begin_cab() override;
|
||||
void gpu_end_cab() override {}
|
||||
void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override;
|
||||
void gpu_end_lamp() override {}
|
||||
void gpu_begin_headlight(std::size_t const Slot) override;
|
||||
void gpu_end_headlight() override {}
|
||||
// the gpu times are those of the pass list
|
||||
void gpu_lamps_timer(bool const Begin) override {}
|
||||
void gpu_headlights_timer(bool const Begin) override {}
|
||||
|
||||
private:
|
||||
// a depth map array of Layers layers of Size x Size, its default view a cube array (Cube) or a 2d array, and a view of each
|
||||
// cube (6 layers) or of each layer
|
||||
bool make(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views, int const Size, int const Layers, bool const Cube, char const *Name);
|
||||
void let_go(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views);
|
||||
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
allocator *m_memory{nullptr};
|
||||
deletion_queue *m_deletions{nullptr};
|
||||
command_stream *m_commands{nullptr};
|
||||
std::uint64_t m_value{0};
|
||||
std::shared_ptr<image> m_cab;
|
||||
std::vector<VkImageView> m_cabviews;
|
||||
std::shared_ptr<image> m_lamps;
|
||||
std::vector<VkImageView> m_lampviews;
|
||||
std::shared_ptr<image> m_headlights;
|
||||
std::vector<VkImageView> m_headlightviews;
|
||||
draw_target m_target;
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
482
rendering/vulkan14/vulkan14sky.cpp
Normal file
482
rendering/vulkan14/vulkan14sky.cpp
Normal file
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
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 light of the sky of the vulkan renderer (the stage P1 of the plan, opengl46_sky and opengl46_exposure of the opengl
|
||||
// renderer): the look-up tables of the physical atmosphere made by compute kernels, the light of the frame worked out from
|
||||
// them and the directional part of the ambient light (gfx::sky_light, shared with the opengl renderer), the environment map
|
||||
// prefiltered with the GGX lobe, the stars of the catalogue; the automatic exposure and the bloom of the post-processing
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/common/exposure.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Timer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
VkPipelineStageFlags2 const shader_stages{VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_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};
|
||||
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
||||
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
|
||||
VkAccessFlags2 const storage_write{VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT};
|
||||
|
||||
std::uint32_t groups(std::uint32_t const Size, std::uint32_t const Group)
|
||||
{
|
||||
return (Size + Group - 1) / Group;
|
||||
}
|
||||
|
||||
// a view of one level of a cube image as a cube, for a kernel writing the level
|
||||
VkImageView level_view(VkDevice Device, vk14::image const &Image, std::uint32_t const Level, std::string const &Name)
|
||||
{
|
||||
VkImageViewCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
info.image = Image.handle();
|
||||
info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
info.format = Image.desc().format;
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, 1, 0, 6};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view)))
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
vk14::name_object(Device, VK_OBJECT_TYPE_IMAGE_VIEW, vk14::handle_value(view), Name);
|
||||
return view;
|
||||
}
|
||||
|
||||
// the push constants of the kernels, laid out as the blocks of the shaders (std430)
|
||||
struct prefilter_constants
|
||||
{
|
||||
float roughness;
|
||||
std::int32_t target_size;
|
||||
float source_size;
|
||||
std::int32_t sample_count;
|
||||
};
|
||||
struct histogram_constants
|
||||
{
|
||||
glm::vec2 log_range;
|
||||
};
|
||||
struct average_constants
|
||||
{
|
||||
glm::vec2 log_range;
|
||||
glm::vec2 pad;
|
||||
glm::vec4 adaptation;
|
||||
glm::vec2 exposure_range;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool vulkan14_renderer::init_sky(std::string &Reason)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
auto &exposure{scene.exposure};
|
||||
auto const device{ctx.device.handle()};
|
||||
auto const frames{static_cast<std::uint32_t>(ctx.frames.size())};
|
||||
|
||||
// the buffers the shading reads
|
||||
if ((false == sky.sky.create(ctx.memory, sizeof(gl::sky_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "sky ambient")) ||
|
||||
(false == sky.atmosphere.create(ctx.memory, sizeof(gl::atmosphere_ubs), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "atmosphere")) ||
|
||||
(false == sky.result.create(ctx.memory, sizeof(gl::atmosphere_result), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, vk14::memory_kind::device, "atmosphere result")) ||
|
||||
(false == sky.result_readback.create(ctx.memory, sizeof(gl::atmosphere_result), frames, "atmosphere result readback")) ||
|
||||
(false == exposure.data.create(ctx.memory, sizeof(gl::exposure_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "exposure")) ||
|
||||
(false == exposure.readback.create(ctx.memory, 4 * sizeof(float), frames, "exposure readback")))
|
||||
{
|
||||
Reason = "no buffers of the light of the sky";
|
||||
return false;
|
||||
}
|
||||
// the tables of the atmosphere, black until they are first made
|
||||
auto make_image = [&](std::shared_ptr<vk14::image> &Image, vk14::image_desc const &Desc, char const *Name) {
|
||||
Image = std::make_shared<vk14::image>();
|
||||
if (false == Image->create(ctx.memory, Desc, Name))
|
||||
{
|
||||
Image.reset();
|
||||
Reason = std::string("no image for ") + Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
{
|
||||
vk14::image_desc desc;
|
||||
desc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
desc.extent = {gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT, 1};
|
||||
if (false == make_image(sky.transmittance, desc, "atmosphere transmittance"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
desc.extent = {gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE, 1};
|
||||
if (false == make_image(sky.multiscattering, desc, "atmosphere multiple scattering"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
desc.extent = {gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT, 1};
|
||||
if (false == make_image(sky.skyview, desc, "atmosphere sky-view"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// the environment map prefiltered with the GGX lobe, made with the environment map
|
||||
if (Global.gfx_envmap_enabled)
|
||||
{
|
||||
vk14::image_desc desc;
|
||||
desc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
||||
desc.layers = 6;
|
||||
desc.extent = {static_cast<std::uint32_t>(gl::ENVMAP_GGX_SIZE), static_cast<std::uint32_t>(gl::ENVMAP_GGX_SIZE), 1};
|
||||
desc.mips = static_cast<std::uint32_t>(gl::ENVMAP_GGX_LEVELS);
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
if (false == make_image(sky.ggx, desc, "environment map ggx"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (std::uint32_t level = 0; level < desc.mips; ++level)
|
||||
{
|
||||
sky.ggx_levels.push_back(level_view(device, *sky.ggx, level, "environment map ggx " + std::to_string(level)));
|
||||
}
|
||||
}
|
||||
// the stars of the catalogue, in the equatorial frame
|
||||
{
|
||||
auto const stars{gfx::weather_state::star_vertices()};
|
||||
sky.star_count = static_cast<std::uint32_t>(stars.size() / 2);
|
||||
if (sky.star_count > 0)
|
||||
{
|
||||
auto vertices{std::make_shared<vk14::buffer>()};
|
||||
auto const size{static_cast<VkDeviceSize>(stars.size() * sizeof(glm::vec4))};
|
||||
if ((false == vertices->create(ctx.memory, size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "stars")) || (false == ctx.uploads.upload_buffer(vertices, stars.data(), size)))
|
||||
{
|
||||
Reason = "no buffer of the stars";
|
||||
return false;
|
||||
}
|
||||
sky.star_vertices = vertices;
|
||||
}
|
||||
}
|
||||
// the kernels and the programs
|
||||
try
|
||||
{
|
||||
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
|
||||
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
|
||||
{
|
||||
Reason = std::string("no compute pipeline of ") + Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if ((false == kernel(sky.transmittance_kernel, "atmosphere_transmittance.comp", "atmosphere transmittance")) || (false == kernel(sky.multiscattering_kernel, "atmosphere_multiscattering.comp", "atmosphere multiple scattering")) ||
|
||||
(false == kernel(sky.skyview_kernel, "atmosphere_skyview.comp", "atmosphere sky-view")) || (false == kernel(sky.ambient_kernel, "atmosphere_ambient.comp", "atmosphere ambient")) ||
|
||||
(false == kernel(sky.prefilter_kernel, "envmap_prefilter.comp", "environment map ggx")) || (false == kernel(exposure.histogram, "exposure_histogram.comp", "exposure histogram")) ||
|
||||
(false == kernel(exposure.average, "exposure_average.comp", "exposure average")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto program = [&](char const *Vertex, char const *Fragment, vk14::vertex_kind const Kind) {
|
||||
auto result{std::make_shared<vk14::scene_program>()};
|
||||
result->vertex = ctx.shaders.get(Vertex);
|
||||
result->fragment = ctx.shaders.get(Fragment);
|
||||
result->fragment_name = Fragment;
|
||||
result->vertices = Kind;
|
||||
result->name = std::string(Vertex) + " + " + Fragment;
|
||||
return result;
|
||||
};
|
||||
sky.atmosphere_dome = program("vbocolor.vert", "sky_atmosphere.frag", vk14::vertex_kind::colour);
|
||||
sky.stars = program("stars.vert", "stars.frag", vk14::vertex_kind::star);
|
||||
for (auto const *name : {"postfx_bloom_down.frag", "postfx_bloom_up.frag"})
|
||||
{
|
||||
scene.post_fragments.emplace(name, ctx.shaders.get(name));
|
||||
}
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
Reason = std::string("invalid shader: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::destroy_sky()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
auto &exposure{scene.exposure};
|
||||
auto const device{ctx.device.handle()};
|
||||
for (auto *kernel : {&sky.transmittance_kernel, &sky.multiscattering_kernel, &sky.skyview_kernel, &sky.ambient_kernel, &sky.prefilter_kernel, &exposure.histogram, &exposure.average})
|
||||
{
|
||||
kernel->destroy(device);
|
||||
}
|
||||
for (auto *view : sky.ggx_levels)
|
||||
{
|
||||
if (view != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
}
|
||||
}
|
||||
sky.ggx_levels.clear();
|
||||
sky.ggx.reset();
|
||||
sky.transmittance.reset();
|
||||
sky.multiscattering.reset();
|
||||
sky.skyview.reset();
|
||||
sky.sky.destroy();
|
||||
sky.atmosphere.destroy();
|
||||
sky.result.destroy();
|
||||
sky.result_readback.destroy();
|
||||
sky.atmosphere_dome.reset();
|
||||
sky.stars.reset();
|
||||
sky.star_vertices.reset();
|
||||
exposure.data.destroy();
|
||||
exposure.readback.destroy();
|
||||
}
|
||||
|
||||
// the images of the sky put in their layouts the first time: black, ready to be read
|
||||
void vulkan14_renderer::clear_sky_images()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
auto const commands{ctx.commands};
|
||||
std::vector<vk14::image_use> uses;
|
||||
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
|
||||
{
|
||||
if (image != nullptr)
|
||||
{
|
||||
uses.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT));
|
||||
}
|
||||
}
|
||||
vk14::barriers(commands, uses);
|
||||
VkClearColorValue const black{{0.f, 0.f, 0.f, 0.f}};
|
||||
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
|
||||
{
|
||||
if (image != nullptr)
|
||||
{
|
||||
VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, image->desc().mips, 0, image->desc().layers};
|
||||
commands->clear_color_image(image->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range);
|
||||
}
|
||||
}
|
||||
uses.clear();
|
||||
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
|
||||
{
|
||||
if (image != nullptr)
|
||||
{
|
||||
uses.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access));
|
||||
}
|
||||
}
|
||||
vk14::barriers(commands, uses);
|
||||
// the exposure starts from the key, not adapted yet
|
||||
gl::exposure_data data{};
|
||||
data.adapted_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.exposure = 1.f;
|
||||
data.average_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.valid = 0;
|
||||
vk14::update_buffer(commands, scene.exposure.data, &data, sizeof(data), 0);
|
||||
// the atmosphere valid from the start, and no directional ambient light
|
||||
vk14::update_buffer(commands, sky.atmosphere, &m_skylight.atmosphere(), sizeof(gl::atmosphere_ubs), 0);
|
||||
vk14::update_buffer(commands, sky.sky, &sky.uploaded, sizeof(gl::sky_data), 0);
|
||||
sky.sky_uploaded = true;
|
||||
}
|
||||
|
||||
// the light of the sky of the frame (opengl46_sky::update()): the tables of the atmosphere made where their input changed, the
|
||||
// result of an earlier frame read back, the sunlight, the ambient light and the fog colour of the frame worked out from it and
|
||||
// the directional ambient light written for the shading. before the passes of the frame
|
||||
void vulkan14_renderer::update_sky()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
auto const commands{ctx.commands};
|
||||
auto const &sun{simulation::Environment.sun()};
|
||||
auto const sundirection{sun.getDirection()};
|
||||
|
||||
m_skylight.begin_update();
|
||||
if (Global.gfx_atmosphere)
|
||||
{
|
||||
bool medium{false};
|
||||
if (m_skylight.atmosphere_changed(glm::normalize(sundirection), medium))
|
||||
{
|
||||
ctx.passes.begin_pass("atmosphere");
|
||||
vk14::update_buffer(commands, sky.atmosphere, &m_skylight.atmosphere(), sizeof(gl::atmosphere_ubs), 0);
|
||||
bind_compute_set(sky.transmittance_kernel);
|
||||
auto const sampled = [&](vk14::image &Image) { return vk14::image_descriptor(ctx.sampling.clamp_linear, Image.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); };
|
||||
if (medium)
|
||||
{
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.transmittance, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
sky.transmittance_kernel.run(commands, {vk14::storage_image(sky.transmittance->view())}, groups(gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, 8), groups(gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT, 8));
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.transmittance, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(*sky.multiscattering, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
sky.multiscattering_kernel.run(commands, {sampled(*sky.transmittance), vk14::storage_image(sky.multiscattering->view())}, gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.multiscattering, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
}
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.skyview, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
sky.skyview_kernel.run(commands, {sampled(*sky.transmittance), sampled(*sky.multiscattering), vk14::storage_image(sky.skyview->view())}, groups(gl::ATMOSPHERE_SKYVIEW_WIDTH, 8), groups(gl::ATMOSPHERE_SKYVIEW_HEIGHT, 8));
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.skyview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT, compute_stage, storage_write);
|
||||
sky.ambient_kernel.run(commands, {sampled(*sky.transmittance), sampled(*sky.skyview), vk14::buffer_descriptor(sky.result.handle(), 0, sizeof(gl::atmosphere_result))}, 1);
|
||||
sky.result_readback.push(commands, ctx.slot, ctx.timelinevalue + 1, sky.result.handle(), 0, sizeof(gl::atmosphere_result));
|
||||
ctx.passes.end_pass();
|
||||
}
|
||||
gl::atmosphere_result result;
|
||||
if (sky.result_readback.pop(ctx.completed(), &result, sizeof(result)))
|
||||
{
|
||||
m_skylight.set_result(result);
|
||||
}
|
||||
}
|
||||
m_skylight.end_update(m_sunlight, m_fogcolor, simulation::Environment.skydome(), sundirection, sun.getAngle());
|
||||
|
||||
// the directional part of the ambient light for the shading
|
||||
auto const &skydata{m_skylight.sky_data()};
|
||||
if ((false == sky.sky_uploaded) || (0 != std::memcmp(&skydata, &sky.uploaded, sizeof(skydata))))
|
||||
{
|
||||
sky.uploaded = skydata;
|
||||
vk14::update_buffer(commands, sky.sky, &sky.uploaded, sizeof(gl::sky_data), 0);
|
||||
sky.sky_uploaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// the levels of the GGX map prefiltered from the environment map with its mipmaps (opengl46_sky::prefilter())
|
||||
void vulkan14_renderer::prefilter_environment()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
if ((false == bool(sky.ggx)) || (false == bool(scene.env_map)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const commands{ctx.commands};
|
||||
ctx.passes.begin_pass("envmap ggx", {vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, sampled_access),
|
||||
vk14::use_of(*sky.ggx, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
for (std::size_t level = 0; level < gl::ENVMAP_GGX_LEVELS; ++level)
|
||||
{
|
||||
auto const size{static_cast<std::int32_t>(gl::ENVMAP_GGX_SIZE >> level)};
|
||||
prefilter_constants constants;
|
||||
constants.roughness = gl::ENVMAP_GGX_ROUGHNESS_MIN + (1.f - gl::ENVMAP_GGX_ROUGHNESS_MIN) * static_cast<float>(level) / static_cast<float>(gl::ENVMAP_GGX_LEVELS - 1);
|
||||
constants.target_size = size;
|
||||
constants.source_size = static_cast<float>(gl::ENVMAP_SIZE);
|
||||
constants.sample_count = 64;
|
||||
auto const count{groups(static_cast<std::uint32_t>(size), static_cast<std::uint32_t>(gl::ENVMAP_GGX_GROUP_SIZE))};
|
||||
sky.prefilter_kernel.dispatch(commands, {vk14::storage_image(sky.ggx_levels[level]), vk14::image_descriptor(ctx.sampling.clamp_linear, scene.env_map->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)}, constants, count, count, 6);
|
||||
}
|
||||
vk14::barriers(commands, {vk14::use_of(*sky.ggx, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
ctx.passes.end_pass();
|
||||
sky.ggx_ready = true;
|
||||
}
|
||||
|
||||
// the stars, in the sky pass (opengl46_weather::draw_stars()). View: the rotation of the camera of the pass
|
||||
void vulkan14_renderer::render_stars(glm::mat4 const &View)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &sky{scene.sky};
|
||||
auto &draw{*m_draw};
|
||||
if ((sky.star_count == 0) || (false == bool(sky.star_vertices)) || (false == bool(sky.stars)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const &sun{simulation::Environment.sun()};
|
||||
glm::mat4 toworld, toview;
|
||||
glm::vec4 params;
|
||||
if (false == m_weatherstate.stars(sun.getAngle(), sun.getSiderealTime(), sun.getLatitude(), View, toworld, toview, params))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (false == bind_scene_pipeline(*sky.stars, *draw.targets, vk14::blend_mode::additive, VK_PRIMITIVE_TOPOLOGY_POINT_LIST))
|
||||
{
|
||||
return;
|
||||
}
|
||||
apply_draw_state();
|
||||
model_ubs.param[0] = params;
|
||||
draw.block = push_draw_block();
|
||||
if (draw.block == ~0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (draw.set_dirty)
|
||||
{
|
||||
bind_pass_set();
|
||||
}
|
||||
if (draw.textures_dirty)
|
||||
{
|
||||
push_textures();
|
||||
}
|
||||
vk14::draw_constants constants;
|
||||
constants.draw = draw.block;
|
||||
for (int column = 0; column < 3; ++column)
|
||||
{
|
||||
constants.data[column] = toworld[column];
|
||||
constants.data[3 + column] = toview[column];
|
||||
}
|
||||
push_constants(&constants);
|
||||
VkDeviceSize const offset{0};
|
||||
auto const buffer{sky.star_vertices->handle()};
|
||||
draw.commands->bind_vertex_buffers(0, 1, &buffer, &offset);
|
||||
draw.commands->draw(sky.star_count, 1, 0, 0);
|
||||
draw.vertices = VK_NULL_HANDLE;
|
||||
draw.userdata = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// the luminance of the image measured and the exposure adapted to it (opengl46_exposure::measure()), outside a rendering
|
||||
void vulkan14_renderer::measure_exposure(vk14::image &Image)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &exposure{ctx.scene.exposure};
|
||||
auto const commands{ctx.commands};
|
||||
float data[4];
|
||||
if (exposure.readback.pop(ctx.completed(), data, sizeof(data)))
|
||||
{
|
||||
std::copy(std::begin(data), std::end(data), std::begin(exposure.values));
|
||||
}
|
||||
vk14::barriers(commands, {vk14::use_of(Image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, sampled_access)});
|
||||
// the reads of the tonemapping of the frame before
|
||||
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT, compute_stage, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | storage_write);
|
||||
auto const buffer{vk14::buffer_descriptor(exposure.data.handle(), 0, sizeof(gl::exposure_data))};
|
||||
histogram_constants histogram{gfx::exposure_settings::histogram_range()};
|
||||
auto const groupsize{2 * gl::EXPOSURE_HISTOGRAM_GROUP_SIZE};
|
||||
exposure.histogram.dispatch(commands, {vk14::image_descriptor(ctx.sampling.clamp_nearest, Image.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), buffer}, histogram, groups(Image.desc().extent.width, groupsize),
|
||||
groups(Image.desc().extent.height, groupsize));
|
||||
vk14::memory_barrier(commands, compute_stage, storage_write, compute_stage, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | storage_write);
|
||||
average_constants average{};
|
||||
average.log_range = gfx::exposure_settings::average_range();
|
||||
average.adaptation = gfx::exposure_settings::adaptation(Timer::GetDeltaRenderTime());
|
||||
average.exposure_range = gfx::exposure_settings::exposure_range();
|
||||
exposure.average.dispatch(commands, {buffer}, average, 1);
|
||||
// read by the tonemapping, and copied for the debug window
|
||||
vk14::memory_barrier(commands, compute_stage, storage_write, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
exposure.readback.push(commands, ctx.slot, ctx.timelinevalue + 1, exposure.data.handle(), offsetof(gl::exposure_data, adapted_luminance), 4 * sizeof(float));
|
||||
}
|
||||
|
||||
// the image taken down and up the bloom chain of the viewport (opengl46_exposure::bloom()); the result, which the tonemapping
|
||||
// blends in, or null without the chain
|
||||
vk14::image *vulkan14_renderer::render_bloom(std::size_t const Index, vk14::image &Image)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &targets{ctx.scene.targets[Index]};
|
||||
if (targets.bloom_sizes.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
// down, the first step from the image itself
|
||||
for (std::size_t level = 0; level < targets.bloom_sizes.size(); ++level)
|
||||
{
|
||||
vk14::draw_constants constants;
|
||||
constants.data[0] = gfx::exposure_settings::bloom_params(level);
|
||||
post_pass("postfx_bloom_down.frag", *targets.bloom_down[level], {level == 0 ? &Image : targets.bloom_down[level - 1].get()}, constants);
|
||||
}
|
||||
// up, from the lowest level
|
||||
for (std::size_t level = targets.bloom_up.size(); level-- > 0;)
|
||||
{
|
||||
auto *lower{(level + 1 < targets.bloom_up.size()) ? targets.bloom_up[level + 1].get() : targets.bloom_down[level + 1].get()};
|
||||
post_pass("postfx_bloom_up.frag", *targets.bloom_up[level], {lower, targets.bloom_down[level].get()}, vk14::draw_constants{});
|
||||
}
|
||||
return targets.bloom_up.empty() ? targets.bloom_down.front().get() : targets.bloom_up.front().get();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14swapchain.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14instance.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "utilities/Globals.h"
|
||||
@@ -360,7 +361,7 @@ void swapchain::presented(VkResult const Result)
|
||||
}
|
||||
}
|
||||
|
||||
void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const
|
||||
void swapchain::record_clear(command_stream *Commands, glm::vec4 const &Colour) const
|
||||
{
|
||||
VkImageMemoryBarrier2 barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
@@ -380,7 +381,7 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
|
||||
VkRenderingAttachmentInfo colour{};
|
||||
colour.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
@@ -396,8 +397,8 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &colour;
|
||||
vkCmdBeginRendering(Commands, &rendering);
|
||||
vkCmdEndRendering(Commands);
|
||||
Commands->begin_rendering(&rendering);
|
||||
Commands->end_rendering();
|
||||
|
||||
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
@@ -405,7 +406,7 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
|
||||
barrier.dstAccessMask = VK_ACCESS_2_NONE;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
Commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
|
||||
void swapchain::release_images()
|
||||
|
||||
@@ -44,7 +44,7 @@ class swapchain
|
||||
// the image of the frame Frame (index of the frame in flight)
|
||||
acquire_result acquire(std::uint32_t const Frame);
|
||||
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
|
||||
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
|
||||
void record_clear(command_stream *Commands, glm::vec4 const &Colour) const;
|
||||
// the image acquired and its view
|
||||
VkImage image() const
|
||||
{
|
||||
|
||||
361
rendering/vulkan14/vulkan14terrain.cpp
Normal file
361
rendering/vulkan14/vulkan14terrain.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14terrain.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::uint32_t mip_count(std::uint32_t const Size)
|
||||
{
|
||||
return static_cast<std::uint32_t>(std::floor(std::log2(std::max(Size, 1u)))) + 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
terrain_materials::~terrain_materials()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void terrain_materials::init(resources const &Resources, copy_draw Draw)
|
||||
{
|
||||
m_resources = &Resources;
|
||||
m_draw = std::move(Draw);
|
||||
}
|
||||
|
||||
void terrain_materials::destroy()
|
||||
{
|
||||
if (m_resources == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const device{m_resources->gpu->handle()};
|
||||
for (auto &images : m_images)
|
||||
{
|
||||
for (auto *view : images.layers)
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
}
|
||||
images = array_images{};
|
||||
}
|
||||
m_resources = nullptr;
|
||||
}
|
||||
|
||||
bool terrain_materials::gpu_init()
|
||||
{
|
||||
return (m_resources != nullptr) && bool(m_draw);
|
||||
}
|
||||
|
||||
vulkan14_texture &terrain_materials::texture(texture_handle const Handle)
|
||||
{
|
||||
return static_cast<vulkan14_texture &>(m_textures.texture(Handle));
|
||||
}
|
||||
|
||||
std::shared_ptr<image> terrain_materials::make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, std::uint32_t const Mips, char const *Name)
|
||||
{
|
||||
image_desc desc;
|
||||
desc.format = Format;
|
||||
desc.extent = {Width, Height, 1};
|
||||
desc.mips = Mips;
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
auto result{std::make_shared<image>()};
|
||||
if (false == result->create(*m_resources->memory, desc, Name))
|
||||
{
|
||||
ErrorLog(std::string("heightmap terrain: no gpu image for ") + Name);
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void terrain_materials::upload(texture_handle const Texture, VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, bool const Mips, VkSampler const Sampler)
|
||||
{
|
||||
auto &target{texture(Texture)};
|
||||
auto map{target.adopted()};
|
||||
auto const generate{Mips && m_resources->uploads->can_blit(Format)};
|
||||
auto const mips{generate ? mip_count(std::max(Width, Height)) : 1u};
|
||||
if ((map == nullptr) || (map->desc().format != Format) || (map->desc().extent.width != Width) || (map->desc().extent.height != Height) || (map->desc().mips != mips))
|
||||
{
|
||||
map = make_image(Format, Width, Height, mips, target.name.c_str());
|
||||
if (map == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// the frames sent before are done with the content the upload replaces before it's written (the barrier of the upload)
|
||||
if (m_resources->uploads->upload(map, Data, Size, {{0, {Width, Height, 1}}}, generate))
|
||||
{
|
||||
target.adopt(map, Sampler);
|
||||
}
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_white(texture_source &Texture)
|
||||
{
|
||||
std::uint8_t const texel[4]{255, 255, 255, 255};
|
||||
auto &target{static_cast<vulkan14_texture &>(Texture)};
|
||||
auto map{make_image(VK_FORMAT_R8G8B8A8_UNORM, 1, 1, 1, target.name.c_str())};
|
||||
if ((map != nullptr) && m_resources->uploads->upload(map, texel, sizeof(texel), {{0, {1, 1, 1}}}, false))
|
||||
{
|
||||
target.adopt(map, m_resources->sampling->repeat_nearest);
|
||||
}
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh)
|
||||
{
|
||||
auto const index{static_cast<std::size_t>(&Array - arrays().data())};
|
||||
auto &images{m_images[index]};
|
||||
let_go(images);
|
||||
image_desc desc;
|
||||
desc.format = (Array.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM);
|
||||
desc.extent = {static_cast<std::uint32_t>(Array.size), static_cast<std::uint32_t>(Array.size), 1};
|
||||
desc.mips = mip_count(static_cast<std::uint32_t>(Array.size));
|
||||
desc.layers = static_cast<std::uint32_t>(Layers);
|
||||
desc.view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
auto &target{texture(Array.handle)};
|
||||
auto map{std::make_shared<image>()};
|
||||
if (false == map->create(*m_resources->memory, desc, target.name))
|
||||
{
|
||||
ErrorLog("heightmap terrain: no gpu image for the layers of \"" + target.name + "\"");
|
||||
return;
|
||||
}
|
||||
auto const device{m_resources->gpu->handle()};
|
||||
for (int layer = 0; layer < Layers; ++layer)
|
||||
{
|
||||
VkImageViewCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
info.image = map->handle();
|
||||
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
info.format = desc.format;
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, static_cast<std::uint32_t>(layer), 1};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
if (vkCreateImageView(device, &info, nullptr, &view) != VK_SUCCESS)
|
||||
{
|
||||
ErrorLog("heightmap terrain: no view of a layer of \"" + target.name + "\"");
|
||||
images.layers.clear();
|
||||
break;
|
||||
}
|
||||
images.layers.push_back(view);
|
||||
}
|
||||
images.map = map;
|
||||
// cleared by the next frame, before anything reads it
|
||||
images.clear = true;
|
||||
target.adopt(map, m_resources->sampling->repeat_aniso);
|
||||
}
|
||||
|
||||
void terrain_materials::let_go(array_images &Images)
|
||||
{
|
||||
if (Images.layers.empty() && (Images.map == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the frames in flight may still read them
|
||||
m_resources->deletions->push(m_resources->uploads->current_value(), [device = m_resources->gpu->handle(), views = std::move(Images.layers), map = std::move(Images.map)]() {
|
||||
for (auto *view : views)
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
}
|
||||
});
|
||||
Images = array_images{};
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_blank(slot const &Slot)
|
||||
{
|
||||
std::uint8_t const blank[4]{0, 0, 0, 0};
|
||||
for (auto const handle : Slot.weights)
|
||||
{
|
||||
upload(handle, VK_FORMAT_R8G8B8A8_UNORM, 1, 1, blank, sizeof(blank), true, m_resources->sampling->clamp_linear);
|
||||
}
|
||||
std::array<float, max_layers * 2 * 4> const zeros{};
|
||||
upload(Slot.table, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast<std::uint32_t>(max_layers), 2, zeros.data(), sizeof(zeros), false, m_resources->sampling->clamp_nearest);
|
||||
// led between the samples, so the edges of the holes run smoothly across the cells
|
||||
std::uint8_t const none{0};
|
||||
upload(Slot.holes, VK_FORMAT_R8_UNORM, 1, 1, &none, 1, false, m_resources->sampling->clamp_linear);
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels)
|
||||
{
|
||||
auto const side{static_cast<std::uint32_t>(Side)};
|
||||
upload(Texture, VK_FORMAT_R8G8B8A8_UNORM, side, side, Texels, static_cast<VkDeviceSize>(side) * side * 4, true, m_resources->sampling->clamp_linear);
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_table(texture_handle const Texture, float const *Table)
|
||||
{
|
||||
upload(Texture, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast<std::uint32_t>(max_layers), 2, Table, max_layers * 2 * 4 * sizeof(float), false, m_resources->sampling->clamp_nearest);
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples)
|
||||
{
|
||||
auto const side{static_cast<std::uint32_t>(Side)};
|
||||
upload(Texture, VK_FORMAT_R8_UNORM, side, side, Samples, static_cast<VkDeviceSize>(side) * side, false, m_resources->sampling->clamp_linear);
|
||||
}
|
||||
|
||||
void terrain_materials::begin_frame(command_stream *const Commands, std::uint64_t const Value)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_value = Value;
|
||||
for (auto &images : m_images)
|
||||
{
|
||||
if ((false == images.clear) || (images.map == nullptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
images.clear = false;
|
||||
// the layers not copied yet read black, as the storage of opengl starts out on most drivers
|
||||
transition(Commands, *images.map, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
VkClearColorValue const zero{};
|
||||
VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
Commands->clear_color_image(images.map->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &range);
|
||||
transition(Commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_begin_copies()
|
||||
{
|
||||
for (auto &images : m_images)
|
||||
{
|
||||
images.drawn = false;
|
||||
}
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default)
|
||||
{
|
||||
auto const index{static_cast<std::size_t>(&Array - arrays().data())};
|
||||
auto &images{m_images[index]};
|
||||
if ((m_commands == nullptr) || (images.map == nullptr) || (static_cast<std::size_t>(Layer) >= images.layers.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (false == images.drawn)
|
||||
{
|
||||
images.drawn = true;
|
||||
transition(m_commands, *images.map, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
|
||||
}
|
||||
auto const view{images.layers[Layer]};
|
||||
auto const format{images.map->desc().format};
|
||||
VkExtent2D const extent{images.map->desc().extent.width, images.map->desc().extent.height};
|
||||
if (Source != nullptr)
|
||||
{
|
||||
auto const *entry{m_resources->images->find(static_cast<vulkan14_texture &>(*Source).get_id())};
|
||||
if (entry != nullptr)
|
||||
{
|
||||
m_draw(m_commands, view, format, extent, entry->target->view(), entry->sampler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// a map the layer doesn't have; one which failed reads as the texture 0 of opengl
|
||||
VkRenderingAttachmentInfo attachment{};
|
||||
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachment.imageView = view;
|
||||
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
if (Source == nullptr)
|
||||
{
|
||||
std::copy(Default.begin(), Default.end(), attachment.clearValue.color.float32);
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.clearValue.color = VkClearColorValue{{0.f, 0.f, 0.f, 1.f}};
|
||||
}
|
||||
VkRenderingInfo rendering{};
|
||||
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
rendering.renderArea = {{0, 0}, extent};
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
m_commands->begin_rendering(&rendering);
|
||||
m_commands->end_rendering();
|
||||
}
|
||||
|
||||
void terrain_materials::gpu_end_copies(bool const Copied)
|
||||
{
|
||||
for (auto &images : m_images)
|
||||
{
|
||||
if ((false == images.drawn) || (images.map == nullptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
images.drawn = false;
|
||||
if (Copied && (images.map->desc().mips > 1) && m_resources->uploads->can_blit(images.map->desc().format))
|
||||
{
|
||||
make_mipmaps(*images.map);
|
||||
}
|
||||
else
|
||||
{
|
||||
transition(m_commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the other levels of every layer made by blits, each from the one before it (glGenerateMipmap of the opengl renderer)
|
||||
void terrain_materials::make_mipmaps(image &Map)
|
||||
{
|
||||
auto const commands{m_commands};
|
||||
auto const layers{Map.desc().layers};
|
||||
auto const mips{Map.desc().mips};
|
||||
auto const extent{Map.desc().extent};
|
||||
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
|
||||
VkImageMemoryBarrier2 entry{};
|
||||
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
entry.srcStageMask = SrcStage;
|
||||
entry.srcAccessMask = SrcAccess;
|
||||
entry.dstStageMask = DstStage;
|
||||
entry.dstAccessMask = DstAccess;
|
||||
entry.oldLayout = From;
|
||||
entry.newLayout = To;
|
||||
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.image = Map.handle();
|
||||
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, Count, 0, layers};
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &entry;
|
||||
commands->pipeline_barrier2(&dependency);
|
||||
};
|
||||
// the first level drawn, the others written by the blits
|
||||
barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
barrier(1, mips - 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
for (std::uint32_t level = 1; level < mips; ++level)
|
||||
{
|
||||
if (level > 1)
|
||||
{
|
||||
barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
}
|
||||
VkImageBlit2 blit{};
|
||||
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
|
||||
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, layers};
|
||||
blit.srcOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> (level - 1), 1u)), static_cast<std::int32_t>(std::max(extent.height >> (level - 1), 1u)), 1};
|
||||
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, layers};
|
||||
blit.dstOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> level, 1u)), static_cast<std::int32_t>(std::max(extent.height >> level, 1u)), 1};
|
||||
VkBlitImageInfo2 info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
|
||||
info.srcImage = Map.handle();
|
||||
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
info.dstImage = Map.handle();
|
||||
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
info.regionCount = 1;
|
||||
info.pRegions = &blit;
|
||||
info.filter = VK_FILTER_LINEAR;
|
||||
commands->blit_image2(&info);
|
||||
}
|
||||
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
Map.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
85
rendering/vulkan14/vulkan14terrain.h
Normal file
85
rendering/vulkan14/vulkan14terrain.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 materials of the heightmap terrain in the vulkan renderer (the stage P6 of the plan, opengl46_terrain_materials): the
|
||||
// textures of gfx::terrain_materials as images of the textures of the bank (vulkan14_texture::adopt()). the weights, the tables
|
||||
// and the holes of the chunks go through the uploads; the layer maps are drawn into the layers of the arrays by the copy shader
|
||||
// (Draw, given by the renderer) in the commands of the frame, the mipmaps of the arrays made by blits after them
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/common/terrainmaterials.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class terrain_materials : public gfx::terrain_materials
|
||||
{
|
||||
public:
|
||||
// the copy shader drawn into Target (a layer of an array in the layout of a colour attachment, of Format and Extent) from the
|
||||
// image Source sampled with Sampler
|
||||
using copy_draw = std::function<void(command_stream *Commands, VkImageView Target, VkFormat Format, VkExtent2D Extent, VkImageView Source, VkSampler Sampler)>;
|
||||
|
||||
terrain_materials(material_manager &Materials, texture_bank &Textures) : gfx::terrain_materials(Materials, Textures) {}
|
||||
~terrain_materials() override;
|
||||
|
||||
// called once, before anything is made
|
||||
void init(resources const &Resources, copy_draw Draw);
|
||||
// the views go (the device is idle)
|
||||
void destroy();
|
||||
// the commands of the frame and the value of the timeline the gpu reaches when it's done with it: the arrays made since the last
|
||||
// frame are cleared. before update()
|
||||
void begin_frame(command_stream *Commands, std::uint64_t Value);
|
||||
// the frame was recorded: no commands go to it any more
|
||||
void end_frame()
|
||||
{
|
||||
m_commands = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_texture(texture_source &Texture, texture_kind const Kind) override {}
|
||||
void gpu_white(texture_source &Texture) override;
|
||||
void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) override;
|
||||
void gpu_blank(slot const &Slot) override;
|
||||
void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) override;
|
||||
void gpu_table(texture_handle const Texture, float const *Table) override;
|
||||
void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) override;
|
||||
void gpu_begin_copies() override;
|
||||
void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) override;
|
||||
void gpu_end_copies(bool const Copied) override;
|
||||
|
||||
private:
|
||||
struct array_images
|
||||
{
|
||||
std::shared_ptr<image> map;
|
||||
std::vector<VkImageView> layers; // the first level of each layer, drawn to
|
||||
bool clear{false}; // made since the last frame, its content not defined yet
|
||||
bool drawn{false}; // a layer was drawn in the copies being made
|
||||
};
|
||||
|
||||
vulkan14_texture &texture(texture_handle const Handle);
|
||||
// an image of Format, Width x Height, a single layer, with Mips levels, sampled and uploaded to
|
||||
std::shared_ptr<image> make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, std::uint32_t const Mips, char const *Name);
|
||||
// Data into the image of the texture, made anew of Format when there is none of the size; mipmaps made of the first level when Mips
|
||||
void upload(texture_handle const Texture, VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, bool const Mips, VkSampler const Sampler);
|
||||
void let_go(array_images &Images);
|
||||
void make_mipmaps(image &Map);
|
||||
|
||||
resources const *m_resources{nullptr};
|
||||
copy_draw m_draw;
|
||||
command_stream *m_commands{nullptr};
|
||||
std::uint64_t m_value{0};
|
||||
std::array<array_images, 3> m_images; // of the arrays: diffuse, normal maps, specular/gloss
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
@@ -42,7 +43,7 @@ bool uploader::can_blit(VkFormat Format) const
|
||||
return result;
|
||||
}
|
||||
|
||||
void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
|
||||
void uploader::begin_frame(command_stream *Commands, std::uint64_t Value)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_value = Value;
|
||||
@@ -57,7 +58,7 @@ void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
|
||||
|
||||
bool uploader::end_frame()
|
||||
{
|
||||
if (m_buffercopies && (m_commands != VK_NULL_HANDLE))
|
||||
if (m_buffercopies && (m_commands != nullptr))
|
||||
{
|
||||
// the draws of the frame read what was copied
|
||||
VkMemoryBarrier2 barrier{};
|
||||
@@ -70,10 +71,10 @@ bool uploader::end_frame()
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.memoryBarrierCount = 1;
|
||||
dependency.pMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(m_commands, &dependency);
|
||||
m_commands->pipeline_barrier2(&dependency);
|
||||
}
|
||||
m_buffercopies = false;
|
||||
m_commands = VK_NULL_HANDLE;
|
||||
m_commands = nullptr;
|
||||
return m_recorded;
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ bool uploader::upload_buffer(std::shared_ptr<buffer> const &Target, void const *
|
||||
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
|
||||
upload.staging->flush(0, Size);
|
||||
m_uploaded += Size;
|
||||
if (m_commands != VK_NULL_HANDLE)
|
||||
if (m_commands != nullptr)
|
||||
{
|
||||
record(upload);
|
||||
}
|
||||
@@ -125,7 +126,7 @@ bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkD
|
||||
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
|
||||
upload.staging->flush(0, Size);
|
||||
m_uploaded += Size;
|
||||
if (m_commands != VK_NULL_HANDLE)
|
||||
if (m_commands != nullptr)
|
||||
{
|
||||
record(upload);
|
||||
}
|
||||
@@ -153,7 +154,7 @@ void uploader::record(job &Job)
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.memoryBarrierCount = 1;
|
||||
dependency.pMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(m_commands, &dependency);
|
||||
m_commands->pipeline_barrier2(&dependency);
|
||||
m_buffercopies = true;
|
||||
}
|
||||
std::vector<VkBufferCopy2> regions;
|
||||
@@ -174,7 +175,7 @@ void uploader::record(job &Job)
|
||||
copy.dstBuffer = Job.buffertarget->handle();
|
||||
copy.regionCount = static_cast<std::uint32_t>(regions.size());
|
||||
copy.pRegions = regions.data();
|
||||
vkCmdCopyBuffer2(m_commands, ©);
|
||||
m_commands->copy_buffer2(©);
|
||||
m_recorded = true;
|
||||
m_deletions->push(m_value, [staging = Job.staging, target = Job.buffertarget]() {});
|
||||
return;
|
||||
@@ -204,7 +205,7 @@ void uploader::record(job &Job)
|
||||
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
copy.regionCount = static_cast<std::uint32_t>(regions.size());
|
||||
copy.pRegions = regions.data();
|
||||
vkCmdCopyBufferToImage2(commands, ©);
|
||||
commands->copy_buffer_to_image2(©);
|
||||
|
||||
auto const given{static_cast<std::uint32_t>(Job.levels.size())};
|
||||
if (Job.mipmaps && (mips > given) && (given > 0))
|
||||
@@ -227,7 +228,7 @@ void uploader::record(job &Job)
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &entry;
|
||||
vkCmdPipelineBarrier2(commands, &dependency);
|
||||
commands->pipeline_barrier2(&dependency);
|
||||
};
|
||||
auto const extent{target.desc().extent};
|
||||
for (std::uint32_t level = given; level < mips; ++level)
|
||||
@@ -249,7 +250,7 @@ void uploader::record(job &Job)
|
||||
info.regionCount = 1;
|
||||
info.pRegions = &blit;
|
||||
info.filter = VK_FILTER_LINEAR;
|
||||
vkCmdBlitImage2(commands, &info);
|
||||
commands->blit_image2(&info);
|
||||
}
|
||||
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
@@ -628,6 +629,31 @@ void vulkan14_texture::apply_pending()
|
||||
}
|
||||
}
|
||||
|
||||
void vulkan14_texture::adopt(std::shared_ptr<image> const &Image, VkSampler const Sampler)
|
||||
{
|
||||
if (m_id != 0)
|
||||
{
|
||||
if (m_image != Image)
|
||||
{
|
||||
m_resources.deletions->push(m_resources.uploads->current_value(), [old = m_image]() {});
|
||||
}
|
||||
m_resources.images->replace(m_id, Image, Sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_id = m_resources.images->add(Image, Sampler);
|
||||
}
|
||||
m_image = Image;
|
||||
m_made = true;
|
||||
if (Image)
|
||||
{
|
||||
data_width = Image->desc().extent.width;
|
||||
data_height = Image->desc().extent.height;
|
||||
}
|
||||
is_ready = true;
|
||||
is_static = true;
|
||||
}
|
||||
|
||||
void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height)
|
||||
{
|
||||
// as the opengl renderer: srgb rgba with mipmaps, the image made anew when the size changes
|
||||
|
||||
@@ -43,7 +43,7 @@ class uploader
|
||||
void init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical);
|
||||
// the commands of the uploads go to Commands until end_frame(); Value: the value of the timeline the gpu reaches when it's done
|
||||
// with the frame. the uploads waiting from between the frames are recorded first
|
||||
void begin_frame(VkCommandBuffer Commands, std::uint64_t Value);
|
||||
void begin_frame(command_stream *Commands, std::uint64_t Value);
|
||||
// whether anything was recorded to the commands of the frame
|
||||
bool end_frame();
|
||||
// the last value of the timeline the gpu may use the objects with: the frame being made, or the last one sent
|
||||
@@ -82,7 +82,7 @@ class uploader
|
||||
allocator const *m_allocator{nullptr};
|
||||
deletion_queue *m_deletions{nullptr};
|
||||
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
|
||||
VkCommandBuffer m_commands{VK_NULL_HANDLE};
|
||||
command_stream *m_commands{nullptr};
|
||||
std::uint64_t m_value{0};
|
||||
bool m_recorded{false};
|
||||
// buffers were copied to in the frame: the barriers before the first copy and after the last one
|
||||
@@ -176,6 +176,13 @@ struct vulkan14_texture : public texture_source
|
||||
|
||||
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
|
||||
void apply_pending();
|
||||
// the image of a texture the renderer fills itself (the textures of the heightmap terrain): sampled with Sampler from now on,
|
||||
// the image it had before goes once the gpu is done with it
|
||||
void adopt(std::shared_ptr<image> const &Image, VkSampler Sampler);
|
||||
std::shared_ptr<image> const &adopted() const
|
||||
{
|
||||
return m_image;
|
||||
}
|
||||
|
||||
protected:
|
||||
void on_failed() override;
|
||||
|
||||
665
rendering/vulkan14/vulkan14weather.cpp
Normal file
665
rendering/vulkan14/vulkan14weather.cpp
Normal file
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
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 weather of the vulkan renderer (the stage P2 of the plan, opengl46_weather of the opengl renderer), drawn from the weather of
|
||||
// the simulation by gfx::weather_state, which it shares with the opengl renderer: the block of the weather of every pass, the
|
||||
// colours of the fog towards the horizon from the sky-view table of the atmosphere, the noises of the clouds, the shadows of the
|
||||
// clouds, the clouds of the main view (blended over frames) and the panorama of the clouds for the other views, the fog volume
|
||||
// of the main view, and the clouds and the fog laid over the sky
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14context.h"
|
||||
#include "rendering/vulkan14/vulkan14commands.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// sizes of the noises (clouds_noise.comp)
|
||||
int const SHAPE_NOISE_SIZE{128};
|
||||
int const DETAIL_NOISE_SIZE{32};
|
||||
int const COVERAGE_NOISE_SIZE{512};
|
||||
|
||||
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_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};
|
||||
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
||||
VkAccessFlags2 const storage_write{VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT};
|
||||
|
||||
std::uint32_t groups(int const Size)
|
||||
{
|
||||
return static_cast<std::uint32_t>((Size + static_cast<int>(gl::WEATHER_GROUP_SIZE) - 1) / static_cast<int>(gl::WEATHER_GROUP_SIZE));
|
||||
}
|
||||
|
||||
// a view of the first level of an image, for a kernel writing it
|
||||
VkImageView first_level_view(VkDevice Device, vk14::image const &Image, std::string const &Name)
|
||||
{
|
||||
VkImageViewCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
info.image = Image.handle();
|
||||
info.viewType = Image.desc().view_type;
|
||||
info.format = Image.desc().format;
|
||||
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, Image.desc().layers};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view)))
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
vk14::name_object(Device, VK_OBJECT_TYPE_IMAGE_VIEW, vk14::handle_value(view), Name);
|
||||
return view;
|
||||
}
|
||||
|
||||
// the push constants and the blocks of the kernels, laid out as those of the shaders
|
||||
struct noise_constants
|
||||
{
|
||||
std::int32_t kind;
|
||||
std::int32_t size;
|
||||
};
|
||||
struct horizon_constants
|
||||
{
|
||||
std::int32_t offset;
|
||||
std::int32_t pad[3];
|
||||
glm::vec4 params;
|
||||
glm::vec3 white_balance;
|
||||
float pad1;
|
||||
glm::vec3 old_color;
|
||||
float pad2;
|
||||
};
|
||||
struct shadow_constants
|
||||
{
|
||||
std::int32_t size;
|
||||
std::int32_t pad[3];
|
||||
glm::vec3 placement;
|
||||
std::int32_t steps;
|
||||
};
|
||||
struct march_constants
|
||||
{
|
||||
std::int32_t mode;
|
||||
std::int32_t pad;
|
||||
glm::ivec2 size;
|
||||
glm::mat4 inverse_projection;
|
||||
glm::mat4 view_to_world;
|
||||
std::int32_t frame;
|
||||
std::int32_t pad1[3];
|
||||
glm::vec4 quality;
|
||||
glm::ivec2 rows;
|
||||
};
|
||||
static_assert(offsetof(march_constants, quality) == 160, "the layout of the constants of clouds_march.comp");
|
||||
struct temporal_constants
|
||||
{
|
||||
glm::ivec2 size;
|
||||
glm::ivec2 pad;
|
||||
glm::mat4 inverse_projection;
|
||||
glm::mat4 view_to_world;
|
||||
glm::mat4 previous_projection_view;
|
||||
glm::vec3 previous_camera;
|
||||
float blend;
|
||||
};
|
||||
static_assert(sizeof(temporal_constants) == 224, "the layout of the constants of clouds_temporal.comp");
|
||||
struct inject_block
|
||||
{
|
||||
glm::ivec3 grid;
|
||||
std::int32_t pad;
|
||||
glm::mat4 inverse_projection;
|
||||
glm::mat4 view_to_world;
|
||||
glm::vec4 slicing;
|
||||
glm::mat4 previous_projection_view;
|
||||
glm::vec3 previous_camera;
|
||||
float pad1;
|
||||
glm::vec4 options;
|
||||
std::int32_t cablamp;
|
||||
std::int32_t pad2;
|
||||
glm::vec2 previous_slicing;
|
||||
glm::vec2 jitter;
|
||||
};
|
||||
static_assert(offsetof(inject_block, cablamp) == 256, "the layout of the block of fog_inject.comp");
|
||||
struct integrate_constants
|
||||
{
|
||||
glm::ivec3 grid;
|
||||
std::int32_t pad;
|
||||
glm::mat4 inverse_projection;
|
||||
glm::mat4 view_to_world;
|
||||
glm::vec4 slicing;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool vulkan14_renderer::init_weather(std::string &Reason)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &weather{scene.weather};
|
||||
auto const device{ctx.device.handle()};
|
||||
// the noises, mipmapped and repeated
|
||||
auto mipmapped = [&](std::shared_ptr<vk14::image> &Image, bool const Volume, int const Size, char const *Name) {
|
||||
vk14::image_desc desc;
|
||||
desc.type = (Volume ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D);
|
||||
desc.view_type = (Volume ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
|
||||
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.extent = {static_cast<std::uint32_t>(Size), static_cast<std::uint32_t>(Size), Volume ? static_cast<std::uint32_t>(Size) : 1u};
|
||||
desc.mips = static_cast<std::uint32_t>(std::floor(std::log2(static_cast<float>(Size)))) + 1;
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
Image = std::make_shared<vk14::image>();
|
||||
if (false == Image->create(ctx.memory, desc, Name))
|
||||
{
|
||||
Image.reset();
|
||||
Reason = std::string("no image for ") + Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if ((false == mipmapped(weather.shape_noise, true, SHAPE_NOISE_SIZE, "cloud shape noise")) || (false == mipmapped(weather.detail_noise, true, DETAIL_NOISE_SIZE, "cloud detail noise")) ||
|
||||
(false == mipmapped(weather.coverage_noise, false, COVERAGE_NOISE_SIZE, "cloud coverage noise")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
{
|
||||
vk14::image_desc desc;
|
||||
desc.format = VK_FORMAT_R32_SFLOAT;
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
weather.depth_dummy = std::make_shared<vk14::image>();
|
||||
if (false == weather.depth_dummy->create(ctx.memory, desc, "clouds panorama depth"))
|
||||
{
|
||||
weather.depth_dummy.reset();
|
||||
Reason = "no image for the depth of the panorama";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
|
||||
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
|
||||
{
|
||||
Reason = std::string("no compute pipeline of ") + Name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if ((false == kernel(weather.noise, "clouds_noise.comp", "clouds noise")) || (false == kernel(weather.march, "clouds_march.comp", "clouds march")) ||
|
||||
(false == kernel(weather.temporal, "clouds_temporal.comp", "clouds temporal")) || (false == kernel(weather.shadow, "clouds_shadow.comp", "clouds shadow")) ||
|
||||
(false == kernel(weather.horizon, "fog_horizon.comp", "fog horizon")) || (false == kernel(weather.inject, "fog_inject.comp", "fog inject")) || (false == kernel(weather.integrate, "fog_integrate.comp", "fog integrate")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
weather.sky = std::make_shared<vk14::scene_program>();
|
||||
weather.sky->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
|
||||
weather.sky->fragment = ctx.shaders.get("sky_weather.frag");
|
||||
weather.sky->fragment_name = "sky_weather.frag";
|
||||
weather.sky->vertices = vk14::vertex_kind::none;
|
||||
weather.sky->name = "quad.vert + sky_weather.frag";
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
Reason = std::string("invalid shader: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::destroy_weather()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &weather{ctx.scene.weather};
|
||||
auto const device{ctx.device.handle()};
|
||||
for (auto *kernel : {&weather.noise, &weather.march, &weather.temporal, &weather.shadow, &weather.horizon, &weather.inject, &weather.integrate})
|
||||
{
|
||||
kernel->destroy(device);
|
||||
}
|
||||
for (auto *image : {&weather.shape_noise, &weather.detail_noise, &weather.coverage_noise, &weather.depth_dummy, &weather.shadow_map, &weather.clouds_color, &weather.clouds_depth, &weather.clouds_history[0], &weather.clouds_history[1], &weather.panorama,
|
||||
&weather.fog_scattering[0], &weather.fog_scattering[1], &weather.fog_integrated})
|
||||
{
|
||||
image->reset();
|
||||
}
|
||||
weather.sky.reset();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// an image of the weather made anew: the old one goes once the gpu is done with the frames which used it (Value)
|
||||
std::shared_ptr<vk14::image> weather_image(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::shared_ptr<vk14::image> &Image, VkImageType const Type, VkFormat const Format, glm::ivec3 const &Size, char const *Name)
|
||||
{
|
||||
if (Image)
|
||||
{
|
||||
Deletions.push(Value, [old = std::move(Image)]() {});
|
||||
}
|
||||
vk14::image_desc desc;
|
||||
desc.type = Type;
|
||||
desc.view_type = (Type == VK_IMAGE_TYPE_3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
|
||||
desc.format = Format;
|
||||
desc.extent = {static_cast<std::uint32_t>(std::max(Size.x, 1)), static_cast<std::uint32_t>(std::max(Size.y, 1)), static_cast<std::uint32_t>(std::max(Size.z, 1))};
|
||||
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
Image = std::make_shared<vk14::image>();
|
||||
if (false == Image->create(Memory, desc, Name))
|
||||
{
|
||||
Image.reset();
|
||||
}
|
||||
return Image;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the three noises, once (opengl46_weather::make_noise())
|
||||
void vulkan14_renderer::make_cloud_noise()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &weather{ctx.scene.weather};
|
||||
auto const commands{ctx.commands};
|
||||
auto const device{ctx.device.handle()};
|
||||
ctx.passes.begin_pass("clouds noise", {vk14::use_of(*weather.shape_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.detail_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write),
|
||||
vk14::use_of(*weather.coverage_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
// the kernel writes the first levels
|
||||
auto const shape{first_level_view(device, *weather.shape_noise, "cloud shape noise level 0")};
|
||||
auto const detail{first_level_view(device, *weather.detail_noise, "cloud detail noise level 0")};
|
||||
auto const coverage{first_level_view(device, *weather.coverage_noise, "cloud coverage noise level 0")};
|
||||
auto const make = [&](int const Kind, VkImageView Volume, int const Size) {
|
||||
weather.noise.dispatch(commands, {vk14::storage_image(Volume), vk14::storage_image(coverage)}, noise_constants{Kind, Size}, groups(Size), groups(Size), Kind == 2 ? 1u : static_cast<std::uint32_t>(Size));
|
||||
vk14::memory_barrier(commands, compute_stage, storage_write, compute_stage, storage_write);
|
||||
};
|
||||
make(0, shape, SHAPE_NOISE_SIZE);
|
||||
make(1, detail, DETAIL_NOISE_SIZE);
|
||||
make(2, shape, COVERAGE_NOISE_SIZE);
|
||||
ctx.deletions.push(ctx.uploads.current_value(), [device, shape, detail, coverage]() {
|
||||
vkDestroyImageView(device, shape, nullptr);
|
||||
vkDestroyImageView(device, detail, nullptr);
|
||||
vkDestroyImageView(device, coverage, nullptr);
|
||||
});
|
||||
// the mipmaps, each level from the one before it
|
||||
for (auto *image : {weather.shape_noise.get(), weather.detail_noise.get(), weather.coverage_noise.get()})
|
||||
{
|
||||
auto const &desc{image->desc()};
|
||||
auto barrier = [&](std::uint32_t Level, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
|
||||
VkImageMemoryBarrier2 entry{};
|
||||
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
entry.srcStageMask = SrcStage;
|
||||
entry.srcAccessMask = SrcAccess;
|
||||
entry.dstStageMask = DstStage;
|
||||
entry.dstAccessMask = DstAccess;
|
||||
entry.oldLayout = From;
|
||||
entry.newLayout = To;
|
||||
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.image = image->handle();
|
||||
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, 1, 0, 1};
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &entry;
|
||||
commands->pipeline_barrier2(&dependency);
|
||||
};
|
||||
barrier(0, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, compute_stage, storage_write, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
for (std::uint32_t level = 1; level < desc.mips; ++level)
|
||||
{
|
||||
barrier(level, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
auto const extent = [&](std::uint32_t Level) {
|
||||
return VkOffset3D{std::max(static_cast<std::int32_t>(desc.extent.width >> Level), 1), std::max(static_cast<std::int32_t>(desc.extent.height >> Level), 1), std::max(static_cast<std::int32_t>(desc.extent.depth >> Level), 1)};
|
||||
};
|
||||
VkImageBlit2 blit{};
|
||||
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
|
||||
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, 1};
|
||||
blit.srcOffsets[1] = extent(level - 1);
|
||||
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1};
|
||||
blit.dstOffsets[1] = extent(level);
|
||||
VkBlitImageInfo2 info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
|
||||
info.srcImage = image->handle();
|
||||
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
info.dstImage = image->handle();
|
||||
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
info.regionCount = 1;
|
||||
info.pRegions = &blit;
|
||||
info.filter = VK_FILTER_LINEAR;
|
||||
commands->blit_image2(&info);
|
||||
barrier(level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
}
|
||||
VkImageMemoryBarrier2 entry{};
|
||||
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
entry.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT;
|
||||
entry.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
entry.dstStageMask = sampled_stage;
|
||||
entry.dstAccessMask = sampled_access;
|
||||
entry.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
entry.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.image = image->handle();
|
||||
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, desc.mips, 0, 1};
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &entry;
|
||||
commands->pipeline_barrier2(&dependency);
|
||||
image->state = vk14::image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access};
|
||||
}
|
||||
ctx.passes.end_pass();
|
||||
weather.noise_made = true;
|
||||
}
|
||||
|
||||
// the block of the weather of the pass to come, from gfx::weather_state (without the colours of the horizon, a block of their own)
|
||||
void vulkan14_renderer::upload_weather_block()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto constexpr size{offsetof(gl::weather_ubs, fog_horizon)};
|
||||
auto const slice{ctx.ring.allocate(size, ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
|
||||
if (false == bool(slice))
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::memcpy(slice.data, &m_weatherstate.block(), size);
|
||||
m_draw->weather_offset = static_cast<std::uint32_t>(slice.offset);
|
||||
m_draw->set_dirty = true;
|
||||
}
|
||||
|
||||
// what the weather of the simulation is for the frame (opengl46_weather::update()): the block, the colours of the horizon, the
|
||||
// shadows of the clouds, a band of the panorama. before the passes of the frame
|
||||
void vulkan14_renderer::update_weather()
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &scene{ctx.scene};
|
||||
auto &weather{scene.weather};
|
||||
auto const commands{ctx.commands};
|
||||
if (false == weather.noise_made)
|
||||
{
|
||||
make_cloud_noise();
|
||||
}
|
||||
m_weatherstate.update(m_skylight.atmosphere_weight(), true, -m_sunlight.direction, glm::vec3{m_sunlight.diffuse} * simulation::Environment.light_intensity(), glm::vec3{m_sunlight.ambient}, m_fogcolor, Global.pCamera.Pos);
|
||||
auto const shadows{m_weatherstate.shadows_wanted()};
|
||||
if (shadows)
|
||||
{
|
||||
if (weather.shadow_size != Global.gfx_clouds_shadows_size)
|
||||
{
|
||||
weather.shadow_size = Global.gfx_clouds_shadows_size;
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.shadow_map, VK_IMAGE_TYPE_2D, VK_FORMAT_R16_SFLOAT, glm::ivec3{weather.shadow_size, weather.shadow_size, 1}, "cloud shadow map");
|
||||
if (weather.shadow_map)
|
||||
{
|
||||
// read before it's first drawn (the pass sets hold it)
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
}
|
||||
}
|
||||
m_weatherstate.place_shadows(weather.shadow_size, Global.pCamera.Pos);
|
||||
}
|
||||
upload_weather_block();
|
||||
|
||||
ctx.passes.begin_pass("weather");
|
||||
bind_compute_set(weather.horizon);
|
||||
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
|
||||
if (m_weatherstate.horizon_on_gpu())
|
||||
{
|
||||
horizon_constants constants{};
|
||||
constants.params = glm::vec4{m_skylight.atmosphere_weight(), m_skylight.sky_scale(), 0.5f * m_skylight.overcast(), 64.f};
|
||||
constants.white_balance = m_skylight.white_balance();
|
||||
constants.old_color = m_fogcolor;
|
||||
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, compute_stage, storage_write);
|
||||
weather.horizon.dispatch(commands, {vk14::buffer_descriptor(scene.weather_horizon.handle(), 0, sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES)}, constants, 1);
|
||||
vk14::memory_barrier(commands, compute_stage, storage_write, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
vk14::update_buffer(commands, scene.weather_horizon, &m_weatherstate.block().fog_horizon[0], sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES, 0);
|
||||
}
|
||||
|
||||
if (shadows && weather.shadow_map)
|
||||
{
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
shadow_constants constants{};
|
||||
constants.size = weather.shadow_size;
|
||||
constants.placement = glm::vec3{m_weatherstate.block().cloud_shadow_params.x, m_weatherstate.block().cloud_shadow_params.y, Global.gfx_clouds_shadows_range};
|
||||
constants.steps = 8;
|
||||
weather.shadow.dispatch(commands, {vk14::storage_image(weather.shadow_map->view())}, constants, groups(weather.shadow_size), groups(weather.shadow_size), 1);
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
m_weatherstate.shadows_drawn(true);
|
||||
upload_weather_block();
|
||||
bind_compute_set(weather.march);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_weatherstate.shadows_drawn(false);
|
||||
}
|
||||
|
||||
// a band of the panorama of the clouds, all of it the first time
|
||||
if (m_weatherstate.clouds_drawn())
|
||||
{
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
if ((weather.panorama_size != level.panorama) || (false == bool(weather.panorama)))
|
||||
{
|
||||
weather.panorama_size = level.panorama;
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.panorama, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{weather.panorama_size, 1}, "clouds panorama");
|
||||
m_weatherstate.panorama_resized();
|
||||
}
|
||||
if (weather.panorama)
|
||||
{
|
||||
int first, rows;
|
||||
m_weatherstate.panorama_band(weather.panorama_size, first, rows);
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.panorama, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.depth_dummy, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
march_constants constants{};
|
||||
constants.mode = 1;
|
||||
constants.size = weather.panorama_size;
|
||||
constants.frame = m_weatherstate.frame();
|
||||
// the panorama is seen blurred: fewer samples
|
||||
constants.quality = glm::vec4{static_cast<float>(std::max(level.steps / 2, 24)), static_cast<float>(std::max(level.light_steps - 1, 2)), Global.gfx_clouds_distance, 0.f};
|
||||
constants.rows = glm::ivec2{first, rows};
|
||||
// the depth isn't written in this mode, but something has to be bound
|
||||
weather.march.dispatch(commands, {vk14::storage_image(weather.panorama->view()), vk14::storage_image(weather.depth_dummy->view())}, constants, groups(weather.panorama_size.x), groups(rows), 1);
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.panorama, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
}
|
||||
}
|
||||
ctx.passes.end_pass();
|
||||
}
|
||||
|
||||
// the clouds and the fog volume of the colour pass of the main view (opengl46_weather::render_view()): Projection and View
|
||||
// (rotation, world axes to the view) of its camera, Size of the target, Cablamp the light of the cab lamp or -1. the lights, the
|
||||
// clusters and the block of the scene of the pass are written, the shadow map drawn. outside a rendering
|
||||
void vulkan14_renderer::render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &weather{ctx.scene.weather};
|
||||
auto const commands{ctx.commands};
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
// the images of the clouds of the main view and of the fog volume, for the size and the quality
|
||||
auto const cloudsize{glm::max((Size + level.divisor - 1) / level.divisor, glm::ivec2{1})};
|
||||
if (m_weatherstate.clouds_drawn() && ((cloudsize != weather.view_size) || (false == bool(weather.clouds_color))))
|
||||
{
|
||||
weather.view_size = cloudsize;
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.clouds_color, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds colour");
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.clouds_depth, VK_IMAGE_TYPE_2D, VK_FORMAT_R32_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds depth");
|
||||
for (auto &history : weather.clouds_history)
|
||||
{
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), history, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds history");
|
||||
}
|
||||
m_weatherstate.previous_clouds.valid = false;
|
||||
}
|
||||
if ((Global.gfx_fog == 1) && ((level.fog_grid != weather.fog_grid) || (false == bool(weather.fog_integrated))))
|
||||
{
|
||||
weather.fog_grid = level.fog_grid;
|
||||
for (auto &scattering : weather.fog_scattering)
|
||||
{
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scattering, VK_IMAGE_TYPE_3D, VK_FORMAT_R16G16B16A16_SFLOAT, weather.fog_grid, "fog scattering");
|
||||
}
|
||||
// one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp)
|
||||
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.fog_integrated, VK_IMAGE_TYPE_3D, VK_FORMAT_R16G16B16A16_SFLOAT, weather.fog_grid + glm::ivec3{0, 0, 1}, "fog volume");
|
||||
if (weather.fog_integrated)
|
||||
{
|
||||
// the pass sets hold it
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
}
|
||||
m_weatherstate.previous_fog.valid = false;
|
||||
}
|
||||
auto const rotation{glm::mat4{glm::mat3{View}}};
|
||||
auto const inverseprojection{glm::inverse(Projection)};
|
||||
auto const viewtoworld{glm::transpose(rotation)};
|
||||
auto const projectionview{Projection * rotation};
|
||||
auto &previousclouds{m_weatherstate.previous_clouds};
|
||||
auto &previousfog{m_weatherstate.previous_fog};
|
||||
|
||||
if (m_weatherstate.clouds_drawn() && weather.clouds_color && weather.clouds_depth && weather.clouds_history[0] && weather.clouds_history[1])
|
||||
{
|
||||
ctx.passes.begin_pass("clouds, fog volume", {vk14::use_of(*weather.clouds_color, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.clouds_depth, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
bind_compute_set(weather.march);
|
||||
march_constants constants{};
|
||||
constants.mode = 0;
|
||||
constants.size = weather.view_size;
|
||||
constants.inverse_projection = inverseprojection;
|
||||
constants.view_to_world = viewtoworld;
|
||||
constants.frame = m_weatherstate.frame();
|
||||
constants.quality = glm::vec4{static_cast<float>(level.steps), static_cast<float>(level.light_steps), Global.gfx_clouds_distance, level.detail ? 1.f : 0.f};
|
||||
constants.rows = glm::ivec2{0, weather.view_size.y};
|
||||
weather.march.dispatch(commands, {vk14::storage_image(weather.clouds_color->view()), vk14::storage_image(weather.clouds_depth->view())}, constants, groups(weather.view_size.x), groups(weather.view_size.y), 1);
|
||||
|
||||
auto const next{1 - weather.clouds_current};
|
||||
auto &history{*weather.clouds_history[weather.clouds_current]};
|
||||
auto &result{*weather.clouds_history[next]};
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.clouds_color, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(*weather.clouds_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(history, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
|
||||
vk14::use_of(result, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
temporal_constants blend{};
|
||||
blend.size = weather.view_size;
|
||||
blend.inverse_projection = inverseprojection;
|
||||
blend.view_to_world = viewtoworld;
|
||||
blend.previous_projection_view = previousclouds.projection_view;
|
||||
blend.previous_camera = glm::vec3{previousclouds.camera - Camera};
|
||||
blend.blend = gfx::weather_state::continuity(previousclouds, Camera) ? 0.15f : 1.f;
|
||||
auto const &linear{ctx.sampling.clamp_linear};
|
||||
auto const &nearest{ctx.sampling.clamp_nearest};
|
||||
weather.temporal.dispatch(commands,
|
||||
{vk14::image_descriptor(linear, weather.clouds_color->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::image_descriptor(nearest, weather.clouds_depth->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
||||
vk14::image_descriptor(linear, history.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(result.view())},
|
||||
blend, groups(weather.view_size.x), groups(weather.view_size.y), 1);
|
||||
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
weather.clouds_current = next;
|
||||
previousclouds = {projectionview, Camera, {}, true};
|
||||
m_weatherstate.clouds_made();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.passes.begin_pass("clouds, fog volume");
|
||||
previousclouds.valid = false;
|
||||
}
|
||||
|
||||
if ((Global.gfx_fog == 1) && weather.fog_integrated && weather.fog_scattering[0] && weather.fog_scattering[1])
|
||||
{
|
||||
bind_compute_set(weather.inject);
|
||||
auto const temporal{Global.gfx_fog_temporal && gfx::weather_state::continuity(previousfog, Camera)};
|
||||
auto const jitter{m_weatherstate.fog_jitter(temporal)};
|
||||
auto const slicing{gfx::weather_state::fog_slicing(weather.fog_grid.z, jitter)};
|
||||
auto const next{1 - weather.fog_current};
|
||||
auto &history{*weather.fog_scattering[weather.fog_current]};
|
||||
auto &result{*weather.fog_scattering[next]};
|
||||
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(history, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
inject_block block{};
|
||||
block.grid = weather.fog_grid;
|
||||
block.inverse_projection = inverseprojection;
|
||||
block.view_to_world = viewtoworld;
|
||||
block.slicing = slicing;
|
||||
block.previous_projection_view = previousfog.projection_view;
|
||||
block.previous_camera = glm::vec3{previousfog.camera - Camera};
|
||||
auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled};
|
||||
auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f};
|
||||
block.options = glm::vec4{share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f};
|
||||
block.cablamp = Cablamp;
|
||||
block.previous_slicing = glm::vec2{previousfog.slicing.x, previousfog.slicing.z};
|
||||
block.jitter = glm::vec2{jitter.x, jitter.y};
|
||||
auto const slice{ctx.ring.allocate(sizeof(block), ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
|
||||
if (slice)
|
||||
{
|
||||
std::memcpy(slice.data, &block, sizeof(block));
|
||||
weather.inject.run(commands,
|
||||
{vk14::storage_image(result.view()), vk14::image_descriptor(ctx.sampling.clamp_linear, history.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::buffer_descriptor(slice.buffer, slice.offset, sizeof(block))},
|
||||
groups(weather.fog_grid.x), groups(weather.fog_grid.y), static_cast<std::uint32_t>(weather.fog_grid.z));
|
||||
}
|
||||
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
|
||||
integrate_constants integrate{};
|
||||
integrate.grid = weather.fog_grid;
|
||||
integrate.inverse_projection = inverseprojection;
|
||||
integrate.view_to_world = viewtoworld;
|
||||
integrate.slicing = slicing;
|
||||
weather.integrate.dispatch(commands, {vk14::image_descriptor(ctx.sampling.clamp_linear, result.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(weather.fog_integrated->view())}, integrate,
|
||||
groups(weather.fog_grid.x), groups(weather.fog_grid.y), 1);
|
||||
vk14::barriers(commands, {vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
|
||||
weather.fog_current = next;
|
||||
previousfog = {projectionview, Camera, slicing, true};
|
||||
m_weatherstate.fog_made(slicing, weather.fog_grid);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousfog.valid = false;
|
||||
}
|
||||
ctx.passes.end_pass();
|
||||
}
|
||||
|
||||
// the clouds and the fog over the sky, in the sky pass (opengl46_weather::draw_sky()). Mainview: the clouds of the main view, the
|
||||
// panorama otherwise
|
||||
void vulkan14_renderer::render_sky_weather(glm::mat4 const &Projection, bool const Mainview)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
auto &weather{ctx.scene.weather};
|
||||
auto &draw{*m_draw};
|
||||
if (false == bool(weather.sky))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int mode{0};
|
||||
vk14::image *image{nullptr};
|
||||
if (m_weatherstate.clouds_drawn())
|
||||
{
|
||||
if (Mainview && m_weatherstate.clouds_view_valid() && weather.clouds_history[weather.clouds_current])
|
||||
{
|
||||
mode = 1;
|
||||
image = weather.clouds_history[weather.clouds_current].get();
|
||||
}
|
||||
else if (weather.panorama)
|
||||
{
|
||||
mode = 2;
|
||||
image = weather.panorama.get();
|
||||
}
|
||||
}
|
||||
// over the sky as it is: (1, source alpha), without the depth
|
||||
auto const glare{draw.glare};
|
||||
draw.glare = true;
|
||||
if (false == bind_scene_pipeline(*weather.sky, *draw.targets, vk14::blend_mode::under_transmittance, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
|
||||
{
|
||||
draw.glare = glare;
|
||||
return;
|
||||
}
|
||||
auto const cull{draw.cullface};
|
||||
draw.cullface = false;
|
||||
apply_draw_state();
|
||||
draw.cullface = cull;
|
||||
draw.glare = glare;
|
||||
draw.block = push_draw_block();
|
||||
if (draw.block == ~0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (draw.set_dirty)
|
||||
{
|
||||
bind_pass_set();
|
||||
}
|
||||
// the clouds pushed as the first texture of the draw, the textures of the walk pushed again for the draws which follow
|
||||
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
|
||||
auto const &scene{ctx.scene};
|
||||
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_linear, scene.white->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
|
||||
if (image != nullptr)
|
||||
{
|
||||
// the panorama repeats around the camera
|
||||
images[0] = VkDescriptorImageInfo{(mode == 2 ? ctx.sampling.panorama : ctx.sampling.clamp_linear), image->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
}
|
||||
scene.material_set.push(draw.commands, scene.scene_layout, images.data());
|
||||
draw.textures_dirty = true;
|
||||
vk14::draw_constants constants;
|
||||
constants.draw = draw.block;
|
||||
auto const inverseprojection{glm::inverse(Projection)};
|
||||
for (int column = 0; column < 4; ++column)
|
||||
{
|
||||
constants.data[column] = inverseprojection[column];
|
||||
}
|
||||
constants.data[4] = glm::vec4{static_cast<float>(mode), 0.f, 0.f, 0.f};
|
||||
push_constants(&constants);
|
||||
draw.commands->draw(4, 1, 0, 0);
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
// fog between the camera and a fragment, the api of shaders/ogl46/apply_fog.glsl. the fog volume and the weather of the opengl
|
||||
// renderer come with the stage P2 of the plan; until then the fog is analytic: exponential with the distance, of the density of
|
||||
// the draw (fog_density of the model block, a draw with 0 is not fogged), of the colour fog_color of the light buffer, turned
|
||||
// towards the colour of the sun around its direction
|
||||
// fog between the camera and a fragment, see fog_volume.glsl and weather_common.glsl. a draw with a fog_density of 0 is not
|
||||
// fogged; one with another density than the regular one (the glare of lamps) gets the optical depth scaled by the ratio
|
||||
#include "fog_volume.glsl"
|
||||
|
||||
// rgb: light the fog adds, a: what it lets through
|
||||
vec4 fog_at_fragment()
|
||||
{
|
||||
if (fog_density <= 0.0)
|
||||
return vec4(0.0, 0.0, 0.0, 1.0);
|
||||
float amount = 1.0 - clamp(exp(-length(f_pos.xyz) * fog_density), 0.0, 1.0);
|
||||
vec3 color = fog_color;
|
||||
if ((lights_count >= 1U) && (lights[0].type == LIGHT_DIR))
|
||||
vec4 fog = fog_along_view(f_pos.xyz, gl_FragCoord.xy);
|
||||
float scale = fog_density / max(fog_light.w, 1e-12);
|
||||
if (abs(scale - 1.0) > 1e-3)
|
||||
{
|
||||
float sunward = pow(max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0), 30.0);
|
||||
color = mix(color, lights[0].color, sunward);
|
||||
float transmittance = exp(log(max(fog.a, 1e-8)) * scale);
|
||||
fog.rgb *= (1.0 - transmittance) / max(1.0 - fog.a, 1e-4);
|
||||
fog.a = transmittance;
|
||||
}
|
||||
return vec4(color * amount, 1.0 - amount);
|
||||
return fog;
|
||||
}
|
||||
|
||||
vec3 get_fog_color()
|
||||
|
||||
130
shaders/vk14/atmosphere_ambient.comp
Normal file
130
shaders/vk14/atmosphere_ambient.comp
Normal file
@@ -0,0 +1,130 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
|
||||
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
|
||||
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
|
||||
// run after the sky-view table; the result is read back by the cpu a few frames later
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_skyview_lut;
|
||||
|
||||
layout(local_size_x = ATMOSPHERE_AMBIENT_GROUP_SIZE) in;
|
||||
|
||||
layout(std430, set = COMPUTE_SET, binding = 2) writeonly buffer atmosphere_result_ssbo
|
||||
{
|
||||
vec4 result_radiance_sh[9];
|
||||
vec4 result_sun_transmittance;
|
||||
vec4 result_horizon;
|
||||
vec4 result_average;
|
||||
vec4 result_zenith_transmittance;
|
||||
};
|
||||
|
||||
// directions of the upper hemisphere each invocation takes
|
||||
const uint DIRECTIONS_PER_INVOCATION = 4U;
|
||||
const uint DIRECTIONS = ATMOSPHERE_AMBIENT_GROUP_SIZE * DIRECTIONS_PER_INVOCATION;
|
||||
// the sky up to this sine of the elevation is the horizon (about 6 degrees)
|
||||
const float HORIZON_BAND = 0.1;
|
||||
|
||||
// partial sums: the 9 coefficients, the light on an upward facing surface, the horizon and its weight, the sky average
|
||||
const int SUMS = 12;
|
||||
shared vec4 shared_sums[ATMOSPHERE_AMBIENT_GROUP_SIZE];
|
||||
|
||||
void sh_basis(vec3 n, out float basis[9])
|
||||
{
|
||||
basis[0] = 0.282095;
|
||||
basis[1] = 0.488603 * n.y;
|
||||
basis[2] = 0.488603 * n.z;
|
||||
basis[3] = 0.488603 * n.x;
|
||||
basis[4] = 1.092548 * n.x * n.y;
|
||||
basis[5] = 1.092548 * n.y * n.z;
|
||||
basis[6] = 0.315392 * (3.0 * n.z * n.z - 1.0);
|
||||
basis[7] = 1.092548 * n.x * n.z;
|
||||
basis[8] = 0.546274 * (n.x * n.x - n.y * n.y);
|
||||
}
|
||||
|
||||
// sum of the value of every invocation, valid in invocation 0
|
||||
vec4 reduce(vec4 value)
|
||||
{
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
barrier();
|
||||
shared_sums[index] = value;
|
||||
barrier();
|
||||
for (uint stride = ATMOSPHERE_AMBIENT_GROUP_SIZE / 2U; stride > 0U; stride >>= 1)
|
||||
{
|
||||
if (index < stride)
|
||||
shared_sums[index] += shared_sums[index + stride];
|
||||
barrier();
|
||||
}
|
||||
return shared_sums[0];
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
vec3 sundirection = normalize(atmo_sun_direction.xyz);
|
||||
|
||||
vec3 sums[SUMS];
|
||||
for (int i = 0; i < SUMS; ++i)
|
||||
sums[i] = vec3(0.0);
|
||||
|
||||
// even spread over the upper hemisphere: a fibonacci spiral in the sine of the elevation
|
||||
const float goldenangle = 2.39996323;
|
||||
const float solidangle = 2.0 * ATMO_PI / float(DIRECTIONS);
|
||||
for (uint k = 0U; k < DIRECTIONS_PER_INVOCATION; ++k)
|
||||
{
|
||||
uint i = index * DIRECTIONS_PER_INVOCATION + k;
|
||||
float y = (float(i) + 0.5) / float(DIRECTIONS);
|
||||
float r = sqrt(max(1.0 - y * y, 0.0));
|
||||
float azimuth = goldenangle * float(i);
|
||||
vec3 direction = vec3(r * cos(azimuth), y, r * sin(azimuth));
|
||||
|
||||
vec3 radiance = textureLod(atmo_skyview_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
|
||||
float basis[9];
|
||||
sh_basis(direction, basis);
|
||||
for (int c = 0; c < 9; ++c)
|
||||
sums[c] += radiance * (basis[c] * solidangle);
|
||||
sums[9] += radiance * (y * solidangle);
|
||||
if (y < HORIZON_BAND)
|
||||
{
|
||||
sums[10] += radiance;
|
||||
sums[11] += vec3(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 total[SUMS];
|
||||
for (int i = 0; i < SUMS; ++i)
|
||||
total[i] = reduce(vec4(sums[i], 0.0)).rgb;
|
||||
|
||||
if (index != 0U)
|
||||
return;
|
||||
|
||||
// the transmittance towards the sun, zero once the planet is in the way
|
||||
float radius = atmo_observer_radius;
|
||||
vec3 origin = vec3(0.0, radius, 0.0);
|
||||
vec3 suntransmittance = texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, sundirection.y)).rgb;
|
||||
if (atmo_ray_sphere_nearest(origin, sundirection, atmo_bottom_radius) >= 0.0)
|
||||
suntransmittance = vec3(0.0);
|
||||
|
||||
// the ground, a lambertian surface lit by the sun and the sky, fills the lower hemisphere with uniform light. its
|
||||
// spherical harmonics have only the constant term and the vertical one: for a uniform radiance L over the lower
|
||||
// hemisphere, 0.282095 * 2 pi * L and 0.488603 * (-pi) * L
|
||||
// the band 0 coefficient of the sky alone over the solid angle of the hemisphere
|
||||
vec3 skyaverage = total[0] / (0.282095 * 2.0 * ATMO_PI);
|
||||
vec3 irradiance = suntransmittance * max(sundirection.y, 0.0) + total[9];
|
||||
vec3 groundradiance = atmo_ground.rgb / ATMO_PI * irradiance;
|
||||
|
||||
total[0] += groundradiance * (0.282095 * 2.0 * ATMO_PI);
|
||||
total[1] += groundradiance * (0.488603 * -ATMO_PI);
|
||||
for (int c = 0; c < 9; ++c)
|
||||
result_radiance_sh[c] = vec4(total[c], 0.0);
|
||||
|
||||
result_sun_transmittance = vec4(suntransmittance, 0.0);
|
||||
result_horizon = vec4(total[10] / max(total[11].x, 1.0), 0.0);
|
||||
result_average = vec4(skyaverage, 0.0);
|
||||
// what the sun high in the sky looks like at the observer, for the white balance
|
||||
result_zenith_transmittance = vec4(texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, 1.0)).rgb, 0.0);
|
||||
}
|
||||
194
shaders/vk14/atmosphere_common.glsl
Normal file
194
shaders/vk14/atmosphere_common.glsl
Normal file
@@ -0,0 +1,194 @@
|
||||
// physically based sky after S. Hillaire, "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
|
||||
// (EGSR 2020): transmittance, multiple scattering and sky-view look-up tables. distances in kilometres, coefficients per
|
||||
// kilometre. the planet is centred at the origin; the look-up tables are worked out in a frame with z up at the observer,
|
||||
// the world (y up) is mapped to it by the callers. radiance is that of a sun of illuminance 1
|
||||
|
||||
layout(std140, set = PASS_SET, binding = ATMOSPHERE_UBO) uniform atmosphere_ubo
|
||||
{
|
||||
// rgb: rayleigh scattering, w: rayleigh scale height
|
||||
vec4 atmo_rayleigh;
|
||||
// x: mie scattering, y: mie extinction, z: mie scale height, w: mie phase asymmetry
|
||||
vec4 atmo_mie;
|
||||
// rgb: ozone absorption, w: altitude of the middle of the ozone layer
|
||||
vec4 atmo_ozone;
|
||||
// rgb: albedo of the ground, w: half width of the ozone layer
|
||||
vec4 atmo_ground;
|
||||
// world space direction towards the sun (y up)
|
||||
vec4 atmo_sun_direction;
|
||||
float atmo_bottom_radius;
|
||||
float atmo_top_radius;
|
||||
// distance of the observer from the centre of the planet
|
||||
float atmo_observer_radius;
|
||||
};
|
||||
|
||||
const float ATMO_PI = 3.14159265359;
|
||||
|
||||
struct atmo_medium_s
|
||||
{
|
||||
vec3 scattering;
|
||||
vec3 extinction;
|
||||
vec3 rayleigh_scattering;
|
||||
vec3 mie_scattering;
|
||||
};
|
||||
|
||||
atmo_medium_s atmo_sample_medium(float radius)
|
||||
{
|
||||
float height = max(radius - atmo_bottom_radius, 0.0);
|
||||
float rayleighdensity = exp(-height / atmo_rayleigh.w);
|
||||
float miedensity = exp(-height / atmo_mie.z);
|
||||
float ozonedensity = max(0.0, 1.0 - abs(height - atmo_ozone.w) / atmo_ground.w);
|
||||
|
||||
atmo_medium_s medium;
|
||||
medium.rayleigh_scattering = atmo_rayleigh.rgb * rayleighdensity;
|
||||
medium.mie_scattering = vec3(atmo_mie.x * miedensity);
|
||||
medium.scattering = medium.rayleigh_scattering + medium.mie_scattering;
|
||||
medium.extinction = medium.rayleigh_scattering + vec3(atmo_mie.y * miedensity) + atmo_ozone.rgb * ozonedensity;
|
||||
return medium;
|
||||
}
|
||||
|
||||
// distance along a ray (unit direction) to the nearest point ahead of the origin where it meets the sphere, -1 if it does not
|
||||
float atmo_ray_sphere_nearest(vec3 origin, vec3 direction, float radius)
|
||||
{
|
||||
float b = dot(origin, direction);
|
||||
float c = dot(origin, origin) - radius * radius;
|
||||
float discriminant = b * b - c;
|
||||
if (discriminant < 0.0)
|
||||
return -1.0;
|
||||
float root = sqrt(discriminant);
|
||||
float t0 = -b - root;
|
||||
float t1 = -b + root;
|
||||
if (t0 < 0.0 && t1 < 0.0)
|
||||
return -1.0;
|
||||
if (t0 < 0.0)
|
||||
return t1;
|
||||
if (t1 < 0.0)
|
||||
return t0;
|
||||
return min(t0, t1);
|
||||
}
|
||||
|
||||
float atmo_rayleigh_phase(float cosangle)
|
||||
{
|
||||
return 3.0 / (16.0 * ATMO_PI) * (1.0 + cosangle * cosangle);
|
||||
}
|
||||
|
||||
// Cornette-Shanks
|
||||
float atmo_mie_phase(float cosangle)
|
||||
{
|
||||
float g = atmo_mie.w;
|
||||
float k = 3.0 / (8.0 * ATMO_PI) * (1.0 - g * g) / (2.0 + g * g);
|
||||
return k * (1.0 + cosangle * cosangle) / pow(max(1.0 + g * g - 2.0 * g * cosangle, 1e-4), 1.5);
|
||||
}
|
||||
|
||||
// texel centres of a table of the given resolution span [0, 1] of the parameter
|
||||
float atmo_unit_to_sub_uv(float u, float resolution)
|
||||
{
|
||||
return (u + 0.5 / resolution) * (resolution / (resolution + 1.0));
|
||||
}
|
||||
|
||||
float atmo_sub_uv_to_unit(float u, float resolution)
|
||||
{
|
||||
return (u - 0.5 / resolution) * (resolution / (resolution - 1.0));
|
||||
}
|
||||
|
||||
// transmittance table: x the distance to the top of the atmosphere along the view, y the height
|
||||
vec2 atmo_transmittance_uv(float radius, float cosviewzenith)
|
||||
{
|
||||
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float rho = sqrt(max(0.0, radius * radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float discriminant = radius * radius * (cosviewzenith * cosviewzenith - 1.0) + atmo_top_radius * atmo_top_radius;
|
||||
float d = max(0.0, -radius * cosviewzenith + sqrt(max(discriminant, 0.0)));
|
||||
float dmin = atmo_top_radius - radius;
|
||||
float dmax = rho + H;
|
||||
return vec2((d - dmin) / max(dmax - dmin, 1e-6), rho / H);
|
||||
}
|
||||
|
||||
void atmo_transmittance_params(vec2 uv, out float radius, out float cosviewzenith)
|
||||
{
|
||||
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
|
||||
float rho = H * uv.y;
|
||||
radius = sqrt(rho * rho + atmo_bottom_radius * atmo_bottom_radius);
|
||||
float dmin = atmo_top_radius - radius;
|
||||
float dmax = rho + H;
|
||||
float d = dmin + uv.x * (dmax - dmin);
|
||||
cosviewzenith = (d == 0.0) ? 1.0 : (H * H - rho * rho - d * d) / (2.0 * radius * d);
|
||||
cosviewzenith = clamp(cosviewzenith, -1.0, 1.0);
|
||||
}
|
||||
|
||||
// multiple scattering table: x the cosine of the sun zenith angle, y the height
|
||||
vec2 atmo_multiscattering_uv(float radius, float cossunzenith)
|
||||
{
|
||||
vec2 uv = vec2(cossunzenith * 0.5 + 0.5, (radius - atmo_bottom_radius) / (atmo_top_radius - atmo_bottom_radius));
|
||||
return clamp(vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE))), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// sky-view table: x the angle between the view and the sun around the zenith (sqrt spaced), y the angle of the view from
|
||||
// the zenith, packed towards the horizon, which splits the table in the sky (lower half) and the ground (upper half)
|
||||
vec2 atmo_skyview_uv(bool intersectsground, float cosviewzenith, float coslightview, float radius)
|
||||
{
|
||||
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
|
||||
float cosbeta = vhorizon / radius;
|
||||
float beta = acos(clamp(cosbeta, -1.0, 1.0));
|
||||
float zenithhorizonangle = ATMO_PI - beta;
|
||||
|
||||
vec2 uv;
|
||||
if (!intersectsground)
|
||||
{
|
||||
float coord = acos(clamp(cosviewzenith, -1.0, 1.0)) / zenithhorizonangle;
|
||||
coord = 1.0 - coord;
|
||||
coord = sqrt(max(coord, 0.0));
|
||||
coord = 1.0 - coord;
|
||||
uv.y = coord * 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
float coord = (acos(clamp(cosviewzenith, -1.0, 1.0)) - zenithhorizonangle) / max(beta, 1e-6);
|
||||
coord = sqrt(max(coord, 0.0));
|
||||
uv.y = coord * 0.5 + 0.5;
|
||||
}
|
||||
uv.x = sqrt(clamp(-coslightview * 0.5 + 0.5, 0.0, 1.0));
|
||||
return vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
|
||||
}
|
||||
|
||||
void atmo_skyview_params(vec2 uv, float radius, out float cosviewzenith, out float coslightview)
|
||||
{
|
||||
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
|
||||
uv = clamp(uv, 0.0, 1.0);
|
||||
|
||||
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
|
||||
float cosbeta = vhorizon / radius;
|
||||
float beta = acos(clamp(cosbeta, -1.0, 1.0));
|
||||
float zenithhorizonangle = ATMO_PI - beta;
|
||||
|
||||
if (uv.y < 0.5)
|
||||
{
|
||||
float coord = 2.0 * uv.y;
|
||||
coord = 1.0 - coord;
|
||||
coord *= coord;
|
||||
coord = 1.0 - coord;
|
||||
cosviewzenith = cos(zenithhorizonangle * coord);
|
||||
}
|
||||
else
|
||||
{
|
||||
float coord = uv.y * 2.0 - 1.0;
|
||||
coord *= coord;
|
||||
cosviewzenith = cos(zenithhorizonangle + beta * coord);
|
||||
}
|
||||
float coord = uv.x * uv.x;
|
||||
coslightview = -(coord * 2.0 - 1.0);
|
||||
}
|
||||
|
||||
// the same mapping for a world space view direction and sun direction (y up), as seen by the observer
|
||||
vec2 atmo_skyview_uv_world(vec3 viewdirection, vec3 sundirection)
|
||||
{
|
||||
float radius = atmo_observer_radius;
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
float cosviewzenith = viewdirection.y;
|
||||
// angle between the view and the sun around the zenith
|
||||
vec2 viewflat = viewdirection.xz;
|
||||
vec2 sunflat = sundirection.xz;
|
||||
float lengths = length(viewflat) * length(sunflat);
|
||||
float coslightview = (lengths > 1e-6) ? clamp(dot(viewflat, sunflat) / lengths, -1.0, 1.0) : 1.0;
|
||||
vec3 localview = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
|
||||
bool intersectsground = atmo_ray_sphere_nearest(origin, localview, atmo_bottom_radius) >= 0.0;
|
||||
return atmo_skyview_uv(intersectsground, cosviewzenith, coslightview, radius);
|
||||
}
|
||||
105
shaders/vk14/atmosphere_integrate.glsl
Normal file
105
shaders/vk14/atmosphere_integrate.glsl
Normal file
@@ -0,0 +1,105 @@
|
||||
// scattering along a ray through the atmosphere, see atmosphere_common.glsl. the includer declares the samplers
|
||||
// atmo_transmittance_lut and (when multiple scattering is used) atmo_multiscattering_lut
|
||||
|
||||
// above the ground by this much (km), so the test for the shadow of the planet does not hit the point itself
|
||||
const float ATMO_PLANET_RADIUS_OFFSET = 0.01;
|
||||
|
||||
struct atmo_scattering_s
|
||||
{
|
||||
// light scattered towards the origin
|
||||
vec3 luminance;
|
||||
// transmittance from the origin to the end of the ray
|
||||
vec3 transmittance;
|
||||
// light scattered once more, as if the light arriving at every point was 1 (multiple scattering table)
|
||||
vec3 multiscattering_as_1;
|
||||
};
|
||||
|
||||
vec3 atmo_transmittance_to_top(float radius, float cossunzenith)
|
||||
{
|
||||
return texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, cossunzenith)).rgb;
|
||||
}
|
||||
|
||||
// Origin and Direction (unit) in the frame of the planet. with Ground the light the ground reflects at the end of the ray
|
||||
// is included. Physicalphase: rayleigh and mie phase functions, otherwise isotropic (multiple scattering table).
|
||||
// Multiscattering: light scattered more than once comes from the multiple scattering table
|
||||
atmo_scattering_s atmo_integrate(vec3 origin, vec3 direction, vec3 sundirection, bool ground, int samples, bool physicalphase, bool multiscattering)
|
||||
{
|
||||
atmo_scattering_s result;
|
||||
result.luminance = vec3(0.0);
|
||||
result.transmittance = vec3(1.0);
|
||||
result.multiscattering_as_1 = vec3(0.0);
|
||||
|
||||
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
|
||||
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
|
||||
float tmax;
|
||||
if (tbottom < 0.0)
|
||||
{
|
||||
if (ttop < 0.0)
|
||||
return result;
|
||||
tmax = ttop;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmax = (ttop > 0.0) ? min(ttop, tbottom) : tbottom;
|
||||
}
|
||||
|
||||
float cosangle = dot(direction, sundirection);
|
||||
const float uniformphase = 1.0 / (4.0 * ATMO_PI);
|
||||
float miephase = physicalphase ? atmo_mie_phase(cosangle) : uniformphase;
|
||||
float rayleighphase = physicalphase ? atmo_rayleigh_phase(cosangle) : uniformphase;
|
||||
|
||||
vec3 throughput = vec3(1.0);
|
||||
float invsamples = 1.0 / float(samples);
|
||||
for (int s = 0; s < samples; ++s)
|
||||
{
|
||||
// the steps grow with the distance: the medium near the observer matters most
|
||||
float t0 = float(s) * invsamples;
|
||||
float t1 = float(s + 1) * invsamples;
|
||||
t0 = t0 * t0 * tmax;
|
||||
t1 = t1 * t1 * tmax;
|
||||
float dt = t1 - t0;
|
||||
float t = t0 + dt * 0.3;
|
||||
|
||||
vec3 position = origin + t * direction;
|
||||
float radius = length(position);
|
||||
vec3 up = position / radius;
|
||||
atmo_medium_s medium = atmo_sample_medium(radius);
|
||||
vec3 extinction = max(medium.extinction, vec3(1e-7));
|
||||
vec3 sampletransmittance = exp(-extinction * dt);
|
||||
|
||||
float cossunzenith = dot(sundirection, up);
|
||||
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
|
||||
float earthshadow = (atmo_ray_sphere_nearest(position + up * ATMO_PLANET_RADIUS_OFFSET, sundirection, atmo_bottom_radius) >= 0.0) ? 0.0 : 1.0;
|
||||
|
||||
vec3 multiscatteredluminance = vec3(0.0);
|
||||
#ifdef ATMO_MULTISCATTERING_LUT
|
||||
if (multiscattering)
|
||||
multiscatteredluminance = texture(atmo_multiscattering_lut, atmo_multiscattering_uv(radius, cossunzenith)).rgb;
|
||||
#endif
|
||||
|
||||
vec3 phasescattering = medium.mie_scattering * miephase + medium.rayleigh_scattering * rayleighphase;
|
||||
vec3 S = earthshadow * suntransmittance * phasescattering + multiscatteredluminance * medium.scattering;
|
||||
// analytic integral over the step of the light scattered at the sample, attenuated on the way to its start
|
||||
vec3 Sint = (S - S * sampletransmittance) / extinction;
|
||||
result.luminance += throughput * Sint;
|
||||
|
||||
vec3 MSint = (medium.scattering - medium.scattering * sampletransmittance) / extinction;
|
||||
result.multiscattering_as_1 += throughput * MSint;
|
||||
|
||||
throughput *= sampletransmittance;
|
||||
}
|
||||
|
||||
if (ground && tbottom > 0.0 && tmax == tbottom)
|
||||
{
|
||||
vec3 position = origin + tbottom * direction;
|
||||
float radius = length(position);
|
||||
vec3 up = position / radius;
|
||||
float cossunzenith = dot(sundirection, up);
|
||||
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
|
||||
float NdotL = clamp(dot(up, sundirection), 0.0, 1.0);
|
||||
result.luminance += suntransmittance * throughput * NdotL * atmo_ground.rgb / ATMO_PI;
|
||||
}
|
||||
|
||||
result.transmittance = throughput;
|
||||
return result;
|
||||
}
|
||||
72
shaders/vk14/atmosphere_multiscattering.comp
Normal file
72
shaders/vk14/atmosphere_multiscattering.comp
Normal file
@@ -0,0 +1,72 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
|
||||
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
|
||||
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
|
||||
#include "atmosphere_integrate.glsl"
|
||||
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 1, rgba16f) writeonly uniform image2D multiscattering_target;
|
||||
|
||||
const int MULTISCATTERING_SAMPLES = 20;
|
||||
const int SQRT_DIRECTIONS = 8;
|
||||
|
||||
shared vec3 shared_luminance[64];
|
||||
shared vec3 shared_multiscattering[64];
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_WorkGroupID.xy);
|
||||
uint index = gl_LocalInvocationID.x;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / float(ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE)));
|
||||
uv = clamp(uv, 0.0, 1.0);
|
||||
|
||||
float cossunzenith = uv.x * 2.0 - 1.0;
|
||||
vec3 sundirection = vec3(0.0, sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), cossunzenith);
|
||||
float radius = atmo_bottom_radius + clamp(uv.y + 1e-4, 0.0, 1.0) * (atmo_top_radius - atmo_bottom_radius - 1e-3);
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
|
||||
// directions spread evenly over the sphere
|
||||
float a = (0.5 + float(index / uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
|
||||
float b = (0.5 + float(index % uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
|
||||
float theta = 2.0 * ATMO_PI * a;
|
||||
float phi = acos(clamp(1.0 - 2.0 * b, -1.0, 1.0));
|
||||
vec3 direction = vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
|
||||
|
||||
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, true, MULTISCATTERING_SAMPLES, false, false);
|
||||
|
||||
const float spheresolidangle = 4.0 * ATMO_PI;
|
||||
const float directions = float(SQRT_DIRECTIONS * SQRT_DIRECTIONS);
|
||||
shared_luminance[index] = result.luminance * spheresolidangle / directions;
|
||||
shared_multiscattering[index] = result.multiscattering_as_1 * spheresolidangle / directions;
|
||||
barrier();
|
||||
|
||||
for (uint stride = 32U; stride > 0U; stride >>= 1)
|
||||
{
|
||||
if (index < stride)
|
||||
{
|
||||
shared_luminance[index] += shared_luminance[index + stride];
|
||||
shared_multiscattering[index] += shared_multiscattering[index + stride];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (index != 0U)
|
||||
return;
|
||||
|
||||
const float isotropicphase = 1.0 / spheresolidangle;
|
||||
vec3 inscatteredluminance = shared_luminance[0] * isotropicphase;
|
||||
vec3 multiscatteringas1 = shared_multiscattering[0] * isotropicphase;
|
||||
// every further order of scattering is the previous one times multiscatteringas1
|
||||
vec3 luminance = inscatteredluminance / max(vec3(1.0) - multiscatteringas1, vec3(1e-3));
|
||||
imageStore(multiscattering_target, texel, vec4(luminance, 1.0));
|
||||
}
|
||||
42
shaders/vk14/atmosphere_skyview.comp
Normal file
42
shaders/vk14/atmosphere_skyview.comp
Normal file
@@ -0,0 +1,42 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
|
||||
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
|
||||
// the atmosphere, after the transmittance and multiple scattering tables
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_multiscattering_lut;
|
||||
#define ATMO_MULTISCATTERING_LUT
|
||||
#include "atmosphere_integrate.glsl"
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 2, rgba16f) writeonly uniform image2D skyview_target;
|
||||
|
||||
const int SKYVIEW_SAMPLES = 32;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (texel.x >= int(ATMOSPHERE_SKYVIEW_WIDTH) || texel.y >= int(ATMOSPHERE_SKYVIEW_HEIGHT))
|
||||
return;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_SKYVIEW_WIDTH, ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
float radius = atmo_observer_radius;
|
||||
float cosviewzenith;
|
||||
float coslightview;
|
||||
atmo_skyview_params(uv, radius, cosviewzenith, coslightview);
|
||||
|
||||
float cossunzenith = clamp(atmo_sun_direction.y, -1.0, 1.0);
|
||||
vec3 sundirection = normalize(vec3(sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), 0.0, cossunzenith));
|
||||
float sinviewzenith = sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0));
|
||||
vec3 direction = vec3(sinviewzenith * coslightview, sinviewzenith * sqrt(max(1.0 - coslightview * coslightview, 0.0)), cosviewzenith);
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
|
||||
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, false, SKYVIEW_SAMPLES, true, true);
|
||||
imageStore(skyview_target, texel, vec4(result.luminance, 1.0));
|
||||
}
|
||||
43
shaders/vk14/atmosphere_transmittance.comp
Normal file
43
shaders/vk14/atmosphere_transmittance.comp
Normal file
@@ -0,0 +1,43 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
|
||||
// one invocation per texel. run when the parameters of the atmosphere change
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform image2D transmittance_target;
|
||||
|
||||
const int TRANSMITTANCE_SAMPLES = 40;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (texel.x >= int(ATMOSPHERE_TRANSMITTANCE_WIDTH) || texel.y >= int(ATMOSPHERE_TRANSMITTANCE_HEIGHT))
|
||||
return;
|
||||
|
||||
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_TRANSMITTANCE_WIDTH, ATMOSPHERE_TRANSMITTANCE_HEIGHT);
|
||||
float radius;
|
||||
float cosviewzenith;
|
||||
atmo_transmittance_params(uv, radius, cosviewzenith);
|
||||
|
||||
vec3 origin = vec3(0.0, 0.0, radius);
|
||||
vec3 direction = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
|
||||
|
||||
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
|
||||
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
|
||||
float tmax = (tbottom > 0.0) ? ((ttop > 0.0) ? min(ttop, tbottom) : tbottom) : max(ttop, 0.0);
|
||||
|
||||
vec3 opticaldepth = vec3(0.0);
|
||||
float dt = tmax / float(TRANSMITTANCE_SAMPLES);
|
||||
for (int s = 0; s < TRANSMITTANCE_SAMPLES; ++s)
|
||||
{
|
||||
vec3 position = origin + (float(s) + 0.5) * dt * direction;
|
||||
opticaldepth += atmo_sample_medium(length(position)).extinction * dt;
|
||||
}
|
||||
|
||||
imageStore(transmittance_target, texel, vec4(exp(-opticaldepth), 1.0));
|
||||
}
|
||||
291
shaders/vk14/clouds_common.glsl
Normal file
291
shaders/vk14/clouds_common.glsl
Normal file
@@ -0,0 +1,291 @@
|
||||
// clouds of the weather (opengl46_weather): density of the layers of weather_common.glsl, made of three tiling noises.
|
||||
// the shape noise (3d, clouds_noise.comp) gives the lumps of a cloud, the detail noise (3d) eats into their edges, the coverage
|
||||
// noise (2d) spreads the clouds of a layer over the sky so they take up its coverage. positions are world offsets from the
|
||||
// camera, in metres, y up; the earth curves away from the camera, so far clouds sink below the horizon
|
||||
#include "weather_common.glsl"
|
||||
|
||||
layout(set = PASS_SET, binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
|
||||
layout(set = PASS_SET, binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise;
|
||||
layout(set = PASS_SET, binding = CLOUD_COVERAGE_TEX) uniform sampler2D cloud_coverage_noise;
|
||||
|
||||
const float CLOUD_EARTH_RADIUS = 6360000.0;
|
||||
// how much of the shape noise is left standing at the edge and in the core of a cloud area (cloud_layer_density)
|
||||
const float CLOUD_FILL_EDGE = 0.45;
|
||||
const float CLOUD_FILL_CORE = 0.85;
|
||||
// how fast the density rises inside the edge of a cloud: above 1 the edges are crisper, the inside keeps its density
|
||||
const float CLOUD_EDGE_SHARPNESS = 3.0;
|
||||
// how far the base and the top of the single clouds stand off those of their layer, up or down, as shares of the depth of the layer
|
||||
// and at most in metres: the clouds of a layer don't all end at one height, some reach a little out of it
|
||||
// how high the base of a cloud curves up at its edge and how far into the cloud area (cloud_coverage: y) it does, and how much higher
|
||||
// the base of a billow is between the cells of the shape noise than in their middles; shares of the height of the cloud
|
||||
const float CLOUD_BASE_ROUND = 0.35;
|
||||
const float CLOUD_BASE_ROUND_DEPTH = 0.3;
|
||||
const float CLOUD_BASE_LUMPS = 0.08;
|
||||
const float CLOUD_BASE_SHIFT = 0.07;
|
||||
const float CLOUD_BASE_SHIFT_MAX = 100.0;
|
||||
const float CLOUD_TOP_SHIFT = 0.15;
|
||||
const float CLOUD_TOP_SHIFT_MAX = 400.0;
|
||||
// the detail noise: its tile against the tile of the shape noise and how deep it eats into the edges (times the erosion of the
|
||||
// genus and the tuning of the debug window, cloud_wind.w); the finer octave of the thin edges: its tile against the detail tile,
|
||||
// how far the coarser octave pushes its lookup about (in its tiles), and the density below which it is used
|
||||
const float CLOUD_DETAIL_SCALE = 6.0;
|
||||
const float CLOUD_EROSION = 2.0;
|
||||
const float CLOUD_FINE_SCALE = 3.7;
|
||||
const float CLOUD_FINE_PUSH = 0.6;
|
||||
const float CLOUD_FINE_BELOW = 0.6;
|
||||
|
||||
float cloud_remap(float value, float low, float high, float newlow, float newhigh)
|
||||
{
|
||||
return newlow + (value - low) / max(high - low, 1e-5) * (newhigh - newlow);
|
||||
}
|
||||
|
||||
// height of a point above the ground reference, the earth curving down with the square of the distance
|
||||
float cloud_height(vec3 position)
|
||||
{
|
||||
return weather_camera.x + position.y - dot(position.xz, position.xz) * (0.5 / CLOUD_EARTH_RADIUS);
|
||||
}
|
||||
|
||||
// distances along a ray (unit Direction from the camera) where it is at Height: the height goes as
|
||||
// camera + up * t - (1 - up^2) / (2 R) * t^2. returns the number of crossings ahead (0-2), sorted into T
|
||||
int cloud_ray_height(float up, float height, out vec2 t)
|
||||
{
|
||||
t = vec2(-1.0);
|
||||
float a = (1.0 - up * up) * (0.5 / CLOUD_EARTH_RADIUS);
|
||||
float b = -up;
|
||||
float c = height - weather_camera.x;
|
||||
// a t^2 + b t + c = 0
|
||||
if (a < 1e-12)
|
||||
{
|
||||
if (abs(b) < 1e-8)
|
||||
return 0;
|
||||
float root = -c / b;
|
||||
t = vec2(root, -1.0);
|
||||
return root > 0.0 ? 1 : 0;
|
||||
}
|
||||
float discriminant = b * b - 4.0 * a * c;
|
||||
if (discriminant < 0.0)
|
||||
return 0;
|
||||
float q = -0.5 * (b + (b >= 0.0 ? 1.0 : -1.0) * sqrt(discriminant));
|
||||
float r0 = q / a;
|
||||
float r1 = (abs(q) > 1e-12) ? c / q : r0;
|
||||
vec2 roots = vec2(min(r0, r1), max(r0, r1));
|
||||
if (roots.y <= 0.0)
|
||||
return 0;
|
||||
if (roots.x <= 0.0)
|
||||
{
|
||||
t = vec2(roots.y, -1.0);
|
||||
return 1;
|
||||
}
|
||||
t = roots;
|
||||
return 2;
|
||||
}
|
||||
|
||||
// the first stretch of a ray within the heights of a layer, x to y; y <= x when it never gets there
|
||||
vec2 cloud_layer_segment(float up, float base, float top)
|
||||
{
|
||||
vec2 tb, tt;
|
||||
int nb = cloud_ray_height(up, base, tb);
|
||||
int nt = cloud_ray_height(up, top, tt);
|
||||
// the crossings in order; the ray goes in and out of the layer at each
|
||||
float crossings[4];
|
||||
int count = 0;
|
||||
for (int i = 0; i < nb; i++)
|
||||
crossings[count++] = tb[i];
|
||||
for (int i = 0; i < nt; i++)
|
||||
crossings[count++] = tt[i];
|
||||
for (int i = 1; i < count; i++)
|
||||
for (int j = i; j > 0 && crossings[j - 1] > crossings[j]; j--)
|
||||
{
|
||||
float swap = crossings[j];
|
||||
crossings[j] = crossings[j - 1];
|
||||
crossings[j - 1] = swap;
|
||||
}
|
||||
bool inside = (weather_camera.x >= base) && (weather_camera.x <= top);
|
||||
float start = 0.0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (inside)
|
||||
return vec2(start, crossings[i]);
|
||||
inside = true;
|
||||
start = crossings[i];
|
||||
}
|
||||
return inside ? vec2(start, 1e9) : vec2(0.0, -1.0);
|
||||
}
|
||||
|
||||
// the position in the noise of a layer: the camera's place in it and the offset from the camera, stretched along the wind
|
||||
vec2 cloud_noise_plane(cloud_layer_s layer, vec2 offset)
|
||||
{
|
||||
vec2 wind = cloud_wind.xy;
|
||||
float along = dot(offset, wind);
|
||||
vec2 across = offset - wind * along;
|
||||
return across + wind * (along / max(layer.motion.w, 1.0));
|
||||
}
|
||||
|
||||
// how far the base (x) and the top (y) of the clouds of a layer may stand off those of the layer, metres
|
||||
vec2 cloud_layer_shift(cloud_layer_s layer)
|
||||
{
|
||||
float thickness = layer.heights.y - layer.heights.x;
|
||||
return vec2(min(thickness * CLOUD_BASE_SHIFT, CLOUD_BASE_SHIFT_MAX), min(thickness * CLOUD_TOP_SHIFT, CLOUD_TOP_SHIFT_MAX));
|
||||
}
|
||||
|
||||
// the lowest base (x) and the highest top (y) the clouds of a layer may reach
|
||||
vec2 cloud_layer_bounds(cloud_layer_s layer)
|
||||
{
|
||||
vec2 shift = cloud_layer_shift(layer);
|
||||
return vec2(layer.heights.x - shift.x, layer.heights.y + shift.y);
|
||||
}
|
||||
|
||||
// share of the layer filled at a place, x: the soft mask of the coverage, y: how far into a cloud area (0 at its edge). Shift: how
|
||||
// far the base (x) and the top (y) of the cloud there stand off those of the layer, from the second and the third noise of the
|
||||
// coverage texture, so the clouds which reach lower don't all reach higher too
|
||||
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset, out vec2 shift)
|
||||
{
|
||||
vec2 coords = (layer.extra.zw + cloud_noise_plane(layer, offset)) * layer.motion.z;
|
||||
vec3 noise = textureLod(cloud_coverage_noise, coords + layer.extra.y, 0.0).rgb;
|
||||
shift = (noise.yz * 2.0 - 1.0) * cloud_layer_shift(layer);
|
||||
float coverage = layer.shape.x;
|
||||
float threshold = 1.0 - coverage;
|
||||
float mask = smoothstep(threshold - 0.06, threshold + 0.06, noise.x);
|
||||
float depth = clamp((noise.x - threshold) / max(coverage, 0.05), 0.0, 1.0);
|
||||
return vec2(mask, depth);
|
||||
}
|
||||
|
||||
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset)
|
||||
{
|
||||
vec2 shift;
|
||||
return cloud_coverage(layer, offset, shift);
|
||||
}
|
||||
|
||||
// extinction (1/m) of a volumetric layer at a point; Detail eats the edges with the small noise
|
||||
float cloud_layer_density(int index, vec3 position, float height, bool detail)
|
||||
{
|
||||
cloud_layer_s layer = cloud_layers[index];
|
||||
vec2 bounds = cloud_layer_bounds(layer);
|
||||
if (height <= bounds.x || height >= bounds.y)
|
||||
return 0.0;
|
||||
vec2 shift;
|
||||
vec2 coverage = cloud_coverage(layer, position.xz, shift);
|
||||
float bottom = layer.heights.x + shift.x;
|
||||
float h = (height - bottom) / max(layer.heights.y + shift.y - bottom, 1.0);
|
||||
if (h <= 0.0 || h >= 1.0)
|
||||
return 0.0;
|
||||
// the anvil spreads the top of the storm clouds out of their area
|
||||
if (layer.profile.w > 0.0)
|
||||
{
|
||||
float anvil = layer.profile.w * smoothstep(0.7, 0.92, h) * (1.0 - smoothstep(0.97, 1.0, h));
|
||||
coverage.x = max(coverage.x, anvil * smoothstep(0.0, 0.5, coverage.y + 0.35));
|
||||
coverage.y = max(coverage.y, anvil * 0.6);
|
||||
}
|
||||
if (coverage.x <= 0.0)
|
||||
return 0.0;
|
||||
// the tops of the single clouds are of different heights, the larger ones (deeper in their area) the taller
|
||||
float top = mix(1.0 - layer.profile.z, 1.0, coverage.y);
|
||||
float fall = 1.0 - smoothstep(top * layer.profile.y, top, h);
|
||||
if (fall <= 0.0)
|
||||
return 0.0;
|
||||
vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz);
|
||||
vec3 coords = vec3(plane.x, height + layer.extra.y * 4096.0, plane.y) * layer.shape.z;
|
||||
vec4 shape = textureLod(cloud_shape_noise, coords, 0.0);
|
||||
// the smaller cells of the worley octaves bite billows into the heaps of the perlin-worley noise; the octaves keep to a narrow
|
||||
// band (clouds_noise.comp: WORLEY_FBM_LOW, WORLEY_FBM_HIGH), spread over 0-1 first
|
||||
float worley = clamp((shape.g * 0.625 + shape.b * 0.25 + shape.a * 0.125 - 0.30) / 0.35, 0.0, 1.0);
|
||||
// the base is not cut flat: it curves up towards the edge of the cloud into its sides, and every billow starts a little higher or
|
||||
// lower; both as shares of the height of the cloud, so the small ones are rounded too and not cut away. the sheets (little
|
||||
// erosion) keep a flatter base
|
||||
float lift = (CLOUD_BASE_ROUND * (1.0 - smoothstep(0.0, CLOUD_BASE_ROUND_DEPTH, coverage.y)) + CLOUD_BASE_LUMPS * (1.0 - worley)) * top * clamp(layer.shape.w * 2.5, 0.0, 1.0);
|
||||
float gradient = smoothstep(lift, lift + layer.profile.x, h) * fall;
|
||||
if (gradient <= 0.0)
|
||||
return 0.0;
|
||||
float base = clamp(cloud_remap(shape.r, (1.0 - worley) * 0.35, 1.0, 0.0, 1.0), 0.0, 1.0);
|
||||
// the sheets (little erosion) keep few holes, the heaps are made of lumps
|
||||
base = mix(1.0, base, clamp(layer.shape.w * 2.5, 0.25, 1.0));
|
||||
// inside its area a cloud is not a solid block: the lumps of the shape noise are cut out of it, the more the nearer its edge,
|
||||
// so it breaks up into single heaps with rounded tops and gaps between them. the sheets keep their base above the cut, and a
|
||||
// layer closing up (broken to overcast) keeps its lumps whole, so it has no more gaps than its coverage
|
||||
float fill = coverage.x * mix(mix(CLOUD_FILL_EDGE, CLOUD_FILL_CORE, coverage.y), 1.0, smoothstep(0.5, 1.0, layer.shape.x));
|
||||
float density = clamp(cloud_remap(base * gradient, 1.0 - fill, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x;
|
||||
if (detail && density > 0.0 && layer.shape.w > 0.0)
|
||||
{
|
||||
vec3 detailcoords = coords * CLOUD_DETAIL_SCALE;
|
||||
vec4 small = textureLod(cloud_detail_noise, detailcoords, 0.0);
|
||||
// spread over 0-1 as the worley octaves of the shape; left in their narrow band they would only thin the cloud evenly
|
||||
float fbm = clamp((small.r * 0.625 + small.g * 0.25 + small.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
|
||||
// the thin outer part of a cloud gets a finer octave too, its lookup pushed about by the coarser one, so the edges are frayed
|
||||
// into small billows and torn bits rather than evenly rounded. the dense inside is never eaten into, so it is skipped there
|
||||
float fineshare = 1.0 - smoothstep(CLOUD_FINE_BELOW * 0.5, CLOUD_FINE_BELOW, density);
|
||||
if (fineshare > 0.0)
|
||||
{
|
||||
vec3 push = (small.gbr - 0.5) * CLOUD_FINE_PUSH;
|
||||
vec4 fine = textureLod(cloud_detail_noise, detailcoords * CLOUD_FINE_SCALE + push, 0.0);
|
||||
float finefbm = clamp((fine.r * 0.625 + fine.g * 0.25 + fine.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
|
||||
fbm = clamp(mix(fbm, fbm * 0.65 + finefbm * 0.35, fineshare), 0.0, 1.0);
|
||||
}
|
||||
// wispy at the base, billowing at the top
|
||||
float erosion = mix(fbm, 1.0 - fbm, clamp(h * 4.0, 0.0, 1.0));
|
||||
density = clamp(cloud_remap(density, erosion * layer.shape.w * CLOUD_EROSION * cloud_wind.w, 1.0, 0.0, 1.0), 0.0, 1.0);
|
||||
}
|
||||
return min(density * CLOUD_EDGE_SHARPNESS, 1.0) * layer.shape.y;
|
||||
}
|
||||
|
||||
// the same without the detail, cheaper, for the shadows and the fog
|
||||
float cloud_density_coarse(vec3 position)
|
||||
{
|
||||
float height = cloud_height(position);
|
||||
float density = 0.0;
|
||||
for (int i = 0; i < int(weather_camera.w); i++)
|
||||
{
|
||||
if (cloud_layers[i].extra.x > 0.5)
|
||||
continue;
|
||||
density += cloud_layer_density(i, position, height, false);
|
||||
}
|
||||
return density;
|
||||
}
|
||||
|
||||
// optical depth of a flat layer (cirrus and the like) where a ray crosses its middle, Up the vertical part of the ray there
|
||||
float cloud_flat_depth(int index, vec3 position)
|
||||
{
|
||||
cloud_layer_s layer = cloud_layers[index];
|
||||
vec2 coverage = cloud_coverage(layer, position.xz);
|
||||
if (coverage.x <= 0.0)
|
||||
return 0.0;
|
||||
vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz);
|
||||
vec2 coords = plane * layer.shape.z;
|
||||
// strands and ripples out of slices of the noise
|
||||
vec4 shape = textureLod(cloud_shape_noise, vec3(coords, 0.37 + layer.extra.y), 0.0);
|
||||
vec4 small = textureLod(cloud_detail_noise, vec3(coords * 4.0, 0.61), 0.0);
|
||||
float strands = clamp(cloud_remap(shape.r * 0.7 + small.r * 0.3, 0.35 * layer.shape.w + (1.0 - coverage.x) * 0.5, 1.0, 0.0, 1.0), 0.0, 1.0);
|
||||
return strands * coverage.x * layer.shape.y;
|
||||
}
|
||||
|
||||
// the shadow of the clouds at a point: what they let through towards the key light. Steps per layer
|
||||
float cloud_transmittance_to_sun(vec3 position, int steps)
|
||||
{
|
||||
vec3 sun = weather_sun_direction.xyz;
|
||||
if (sun.y <= 0.01)
|
||||
return fog_light.y;
|
||||
float height = cloud_height(position);
|
||||
float depth = 0.0;
|
||||
for (int i = 0; i < int(weather_camera.w); i++)
|
||||
{
|
||||
cloud_layer_s layer = cloud_layers[i];
|
||||
if (layer.extra.x > 0.5)
|
||||
{
|
||||
float middle = 0.5 * (layer.heights.x + layer.heights.y);
|
||||
if (middle > height)
|
||||
depth += cloud_flat_depth(i, position + sun * ((middle - height) / sun.y)) / sun.y;
|
||||
continue;
|
||||
}
|
||||
vec2 bounds = cloud_layer_bounds(layer);
|
||||
float enter = max(bounds.x - height, 0.0) / sun.y;
|
||||
float leave = (bounds.y - height) / sun.y;
|
||||
if (leave <= 0.0)
|
||||
continue;
|
||||
float step = (leave - enter) / float(steps);
|
||||
for (int s = 0; s < steps; s++)
|
||||
{
|
||||
vec3 point = position + sun * (enter + step * (float(s) + 0.5));
|
||||
depth += cloud_layer_density(i, point, cloud_height(point), false) * step;
|
||||
}
|
||||
}
|
||||
return exp(-depth);
|
||||
}
|
||||
295
shaders/vk14/clouds_march.comp
Normal file
295
shaders/vk14/clouds_march.comp
Normal file
@@ -0,0 +1,295 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// clouds of the weather ray marched through their layers (clouds_common.glsl), into an image the sky pass lays over the sky
|
||||
// (sky_weather.frag). mode 0: the view of the main camera at a part of the resolution, smoothed over frames by
|
||||
// clouds_temporal.comp; mode 1: a panorama around the camera (azimuth across, elevation up), a band of rows per frame, for the
|
||||
// environment map and the other views. rgb: the light of the clouds with the fog in front of them, a: what they let through
|
||||
// of the sky behind; the depth image gets the distance of the clouds, for the reprojection
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "clouds_common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
layout(rgba16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D clouds_color;
|
||||
layout(r32f, set = COMPUTE_SET, binding = 1) uniform writeonly image2D clouds_depth;
|
||||
|
||||
// the parameters of the kernel (push constants)
|
||||
layout(push_constant) uniform kernel_constants
|
||||
{
|
||||
int mode;
|
||||
ivec2 size;
|
||||
// mode 0: clip space to view space of the camera, and view space to world axes
|
||||
mat4 inverse_projection;
|
||||
mat4 view_to_world;
|
||||
int frame;
|
||||
// x: samples through a layer at most, y: samples towards the sun, z: farthest distance (m), w: 1 with the detail noise
|
||||
vec4 quality;
|
||||
// mode 1: first row of the band and the rows in it
|
||||
ivec2 rows;
|
||||
};
|
||||
|
||||
// lowest elevation of the panorama (radians); its rows go with the square of the height, denser at the horizon
|
||||
const float PANORAMA_LOW = -0.1;
|
||||
// scales of the light of the clouds against the shading of the surfaces (light_common.glsl: SUN_DIFFUSE_SCALE, AMBIENT_SCALE),
|
||||
// times the tuning of the debug window (weather_sun_color.w, weather_ambient.w). the sunlight three times that of the sunlit faces,
|
||||
// set by eye in the simulator: the sunlit sides of real cumulus are several times brighter than the sky around them. the sky light
|
||||
// about as the shaded faces get it: more of it greys the shaded sides of the clouds into a flat pastel
|
||||
const float CLOUD_SUN_SCALE = 4.5;
|
||||
const float CLOUD_AMBIENT_SCALE = 0.6;
|
||||
// the ground under the clouds lights their bases back with this share of the light falling on it
|
||||
const float CLOUD_GROUND_ALBEDO = 0.2;
|
||||
// how fast the light of the sky fades into a cloud, per optical depth of the cloud below a point, or above it past CLOUD_SKY_REACH
|
||||
const float CLOUD_AMBIENT_FADE = 0.05;
|
||||
// the part of the cloud over a point looked at for the sky light held back (cloud_sky_depth), metres, how fast the light fades
|
||||
// per optical depth there (less than the extinction: the light scattered many times gets through further), and the share left
|
||||
// in the darkest places
|
||||
const float CLOUD_SKY_REACH = 400.0;
|
||||
const float CLOUD_SKY_FADE = 0.25;
|
||||
const float CLOUD_SKY_FLOOR = 0.15;
|
||||
// how dense a cloud has to be to scatter the sunlight back at full: the thin edges and wisps of the side lit by the sun, seen with the
|
||||
// sun behind, are darker than its bulk (the "powder" effect: the light takes a few scatterings to turn back towards the view)
|
||||
const float CLOUD_POWDER = 6.0;
|
||||
// how many of the nearest steps towards the sun see the detail noise
|
||||
const int CLOUD_LIGHT_DETAIL_STEPS = 1;
|
||||
// orders of the scattering the sunlight is followed through (cloud_sunlight): the higher ones reach deep into a thick cloud, so its
|
||||
// shaded sides and its inside are lit through the cloud too, not only by the sky, and a big cloud close by is not a flat grey
|
||||
const int CLOUD_SCATTER_OCTAVES = 6;
|
||||
// how much longer every next step towards the sun is than the one before: 1 + this / steps², so the first step is short where the
|
||||
// light falls off fastest (about a 30th of the reach at 4 steps) and the steps get close to even when there are many of them
|
||||
const float CLOUD_LIGHT_GROWTH = 24.0;
|
||||
|
||||
float henyey_greenstein(float cosangle, float g)
|
||||
{
|
||||
// times 4 pi: 1 for an even scattering
|
||||
float g2 = g * g;
|
||||
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
|
||||
}
|
||||
|
||||
float cloud_phase(float cosangle, float scale)
|
||||
{
|
||||
return mix(henyey_greenstein(cosangle, 0.8 * scale), henyey_greenstein(cosangle, -0.25 * scale), 0.3);
|
||||
}
|
||||
|
||||
// the light of the sky a face looking straight up (Up 1) or down (-1) gets: the uniform ambient light and the directional part of
|
||||
// the sky light (sky_ssbo, light_common.glsl: sky_ambient_directional) along the vertical. with the physical atmosphere it is bluer
|
||||
// and brighter from above than from the sides, so the shaded sides of the clouds take the colour of the sky above them
|
||||
vec3 cloud_sky_light(float up)
|
||||
{
|
||||
vec3 directional = sky_ambient_sh[1].rgb * (0.488603 * up) - sky_ambient_sh[6].rgb * 0.315392 - sky_ambient_sh[8].rgb * 0.546274;
|
||||
return max(weather_ambient.rgb + directional * 1.3, vec3(0.0)) * (CLOUD_AMBIENT_SCALE * weather_ambient.w);
|
||||
}
|
||||
|
||||
// light of the sun reaching a point inside a layer through the cloud towards it, with the light scattered many times
|
||||
// (octaves after Wrenninge), and the phase of the view
|
||||
float cloud_sunlight(int index, vec3 position, float cosangle, int steps, float powder, bool detail)
|
||||
{
|
||||
vec3 sun = weather_sun_direction.xyz;
|
||||
cloud_layer_s layer = cloud_layers[index];
|
||||
float thickness = layer.heights.y - layer.heights.x;
|
||||
float reach = min(thickness, 1500.0);
|
||||
float depth = 0.0;
|
||||
float travelled = 0.0;
|
||||
// steps growing geometrically: near the point the shape matters, far only the bulk. with steps growing evenly the first one was a
|
||||
// tenth of the reach at 4 steps, and the light fell off the smooth sides of the clouds in visible bands
|
||||
float growth = 1.0 + CLOUD_LIGHT_GROWTH / float(steps * steps);
|
||||
float step = reach * (growth - 1.0) / (pow(growth, float(steps)) - 1.0);
|
||||
for (int s = 0; s < steps; s++)
|
||||
{
|
||||
vec3 point = position + sun * (travelled + 0.5 * step);
|
||||
travelled += step;
|
||||
// the nearest steps with the detail noise: the small billows shade one another, lit on top and grey beneath
|
||||
depth += cloud_layer_density(index, point, cloud_height(point), detail && (s < CLOUD_LIGHT_DETAIL_STEPS)) * step;
|
||||
step *= growth;
|
||||
}
|
||||
float light = 0.0;
|
||||
float amount = 1.0;
|
||||
float fade = 1.0;
|
||||
float spread = 1.0;
|
||||
for (int octave = 0; octave < CLOUD_SCATTER_OCTAVES; octave++)
|
||||
{
|
||||
light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread) * (octave == 0 ? powder : 1.0);
|
||||
amount *= 0.5;
|
||||
fade *= 0.5;
|
||||
spread *= 0.5;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
|
||||
// optical depth of the cloud over a point up to Reach above it, from Samples of the coarse density: under a lump and deep in a
|
||||
// thick cloud the sky light is held back, at the tops and the thin edges it is not, so the shaded sides take the form of the cloud
|
||||
float cloud_sky_depth(int index, vec3 position, float height, float reach, int samples)
|
||||
{
|
||||
float depth = 0.0;
|
||||
for (int s = 0; s < samples; s++)
|
||||
{
|
||||
// up along the vertical: the height grows by the same distance (cloud_height bends only with the horizontal distance)
|
||||
float up = reach * (float(s) + 0.5) / float(samples);
|
||||
depth += cloud_layer_density(index, position + vec3(0.0, up, 0.0), height + up, false);
|
||||
}
|
||||
return depth * reach / float(samples);
|
||||
}
|
||||
|
||||
float interleaved_noise(vec2 pixel)
|
||||
{
|
||||
return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
vec3 direction;
|
||||
if (mode == 0)
|
||||
{
|
||||
if (any(greaterThanEqual(texel, size)))
|
||||
return;
|
||||
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
|
||||
// reverse z: the near plane at 1
|
||||
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
|
||||
direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
|
||||
}
|
||||
else
|
||||
{
|
||||
texel.y += rows.x;
|
||||
if (texel.x >= size.x || texel.y >= min(size.y, rows.x + rows.y))
|
||||
return;
|
||||
vec2 coords = (vec2(texel) + 0.5) / vec2(size);
|
||||
float elevation = PANORAMA_LOW + (0.5 * WEATHER_PI - PANORAMA_LOW) * coords.y * coords.y;
|
||||
float azimuth = coords.x * 2.0 * WEATHER_PI;
|
||||
direction = vec3(cos(elevation) * sin(azimuth), sin(elevation), cos(elevation) * cos(azimuth));
|
||||
}
|
||||
|
||||
vec3 sun = weather_sun_direction.xyz;
|
||||
float cosangle = dot(direction, sun);
|
||||
float sunup = weather_sun_direction.w;
|
||||
float maxdistance = quality.z;
|
||||
int maxsteps = int(quality.x);
|
||||
int lightsteps = int(quality.y);
|
||||
int skysteps = (lightsteps >= 4) ? 2 : 1;
|
||||
bool detail = quality.w > 0.5;
|
||||
float jitter = fract(interleaved_noise(vec2(texel)) + float(frame) * 0.618034);
|
||||
|
||||
vec3 scattered = vec3(0.0);
|
||||
float transmittance = 1.0;
|
||||
float depthsum = 0.0;
|
||||
float weightsum = 0.0;
|
||||
int layers = int(weather_camera.w);
|
||||
|
||||
// the stretches of the volumetric layers along the ray, nearest first
|
||||
vec2 segments[MAX_CLOUD_LAYERS];
|
||||
int order[MAX_CLOUD_LAYERS];
|
||||
int count = 0;
|
||||
for (int i = 0; i < layers; i++)
|
||||
{
|
||||
if (cloud_layers[i].extra.x > 0.5)
|
||||
continue;
|
||||
vec2 bounds = cloud_layer_bounds(cloud_layers[i]);
|
||||
vec2 segment = cloud_layer_segment(direction.y, bounds.x, bounds.y);
|
||||
segment.y = min(segment.y, maxdistance);
|
||||
if (segment.y <= segment.x)
|
||||
continue;
|
||||
int at = count++;
|
||||
segments[at] = segment;
|
||||
order[at] = i;
|
||||
for (int j = at; j > 0 && segments[j - 1].x > segments[j].x; j--)
|
||||
{
|
||||
vec2 swapsegment = segments[j];
|
||||
segments[j] = segments[j - 1];
|
||||
segments[j - 1] = swapsegment;
|
||||
int swaporder = order[j];
|
||||
order[j] = order[j - 1];
|
||||
order[j - 1] = swaporder;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 ambienttop = cloud_sky_light(1.0);
|
||||
vec3 sunlight = weather_sun_color.rgb * (CLOUD_SUN_SCALE * weather_sun_color.w) * sunup;
|
||||
// the bases are lit from below by the ground, which the sun lights through the gaps between the clouds
|
||||
vec3 ambientbottom = (weather_sun_color.rgb * max(weather_sun_direction.y, 0.0) * sunup * fog_light.y + ambienttop) * CLOUD_GROUND_ALBEDO;
|
||||
// seen against the sun the thin edges glow (the forward scattering); seen with the sun behind, they are dark
|
||||
float powderview = clamp(1.0 - cosangle, 0.0, 1.0);
|
||||
for (int k = 0; k < count && transmittance > 0.01; k++)
|
||||
{
|
||||
int index = order[k];
|
||||
cloud_layer_s layer = cloud_layers[index];
|
||||
vec2 segment = segments[k];
|
||||
float span = segment.y - segment.x;
|
||||
float thickness = layer.heights.y - layer.heights.x;
|
||||
int steps = clamp(int(float(maxsteps) * span / (thickness * 4.0)), 8, maxsteps);
|
||||
float step = span / float(steps);
|
||||
for (int s = 0; s < steps; s++)
|
||||
{
|
||||
float t = segment.x + (float(s) + jitter) * step;
|
||||
vec3 position = direction * t;
|
||||
float height = cloud_height(position);
|
||||
float density = cloud_layer_density(index, position, height, detail);
|
||||
if (density <= 0.0)
|
||||
continue;
|
||||
float powder = mix(1.0, 1.0 - exp(-density / max(layer.shape.y, 1e-6) * CLOUD_POWDER), powderview);
|
||||
float sunshare = (sunup > 0.0) ? cloud_sunlight(index, position, cosangle, lightsteps, powder, detail) : 0.0;
|
||||
// the light of the sky reaches inside through the cloud above, that of the ground through the cloud below: the base of a
|
||||
// thick cloud is darker than its top, and greyer, lit only by the ground and the sky at its sides
|
||||
float column = layer.shape.y * layer.shape.x;
|
||||
float top = cloud_layer_bounds(layer).y;
|
||||
float reach = clamp(top - height, 0.0, CLOUD_SKY_REACH);
|
||||
float sky = exp(-CLOUD_SKY_FADE * cloud_sky_depth(index, position, height, reach, skysteps) - CLOUD_AMBIENT_FADE * column * max(top - height - reach, 0.0));
|
||||
float below = column * max(height - layer.heights.x, 0.0);
|
||||
vec3 light = sunlight * sunshare + (ambienttop * mix(CLOUD_SKY_FLOOR, 1.0, sky) + ambientbottom * exp(-CLOUD_AMBIENT_FADE * below));
|
||||
float through = exp(-density * step);
|
||||
vec3 inscattered = light * (1.0 - through);
|
||||
scattered += transmittance * inscattered;
|
||||
float weight = transmittance * (1.0 - through);
|
||||
depthsum += t * weight;
|
||||
weightsum += weight;
|
||||
transmittance *= through;
|
||||
if (transmittance < 0.01)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// the thin high layers, past the others
|
||||
for (int i = 0; i < layers && transmittance > 0.01; i++)
|
||||
{
|
||||
cloud_layer_s layer = cloud_layers[i];
|
||||
if (layer.extra.x < 0.5)
|
||||
continue;
|
||||
vec2 crossing;
|
||||
if (cloud_ray_height(direction.y, 0.5 * (layer.heights.x + layer.heights.y), crossing) == 0)
|
||||
continue;
|
||||
float t = crossing.x;
|
||||
if (t > maxdistance * 2.0)
|
||||
continue;
|
||||
vec3 position = direction * t;
|
||||
// the slant of the ray through the sheet where it crosses it, the earth curving away
|
||||
float slope = max(abs(direction.y - (1.0 - direction.y * direction.y) * t / CLOUD_EARTH_RADIUS), 0.03);
|
||||
float depth = cloud_flat_depth(i, position) / slope;
|
||||
if (depth <= 0.0)
|
||||
continue;
|
||||
float through = exp(-depth);
|
||||
// ice crystals scatter forwards strongly
|
||||
vec3 light = sunlight * henyey_greenstein(cosangle, 0.6) * 0.8 + ambienttop;
|
||||
scattered += transmittance * light * (1.0 - through);
|
||||
float weight = transmittance * (1.0 - through);
|
||||
depthsum += t * weight;
|
||||
weightsum += weight;
|
||||
transmittance *= through;
|
||||
}
|
||||
|
||||
// the haze the atmosphere has in the sky already lies between the camera and the clouds too; the rest of the fog goes over
|
||||
// the clouds and the sky alike when they are laid over it (sky_weather.frag)
|
||||
float span_distance = (weightsum > 1e-5) ? depthsum / weightsum : maxdistance;
|
||||
if (weightsum > 1e-5)
|
||||
{
|
||||
vec4 fog = fog_analytic_with(direction, 0.0, span_distance, min(fog_haze.x, fog_haze.z), 0.0);
|
||||
scattered = scattered * fog.a + fog.rgb * (1.0 - transmittance);
|
||||
}
|
||||
if (any(isnan(scattered)) || isnan(transmittance))
|
||||
{
|
||||
scattered = vec3(0.0);
|
||||
transmittance = 1.0;
|
||||
}
|
||||
imageStore(clouds_color, texel, vec4(scattered, transmittance));
|
||||
if (mode == 0)
|
||||
imageStore(clouds_depth, texel, vec4(span_distance));
|
||||
}
|
||||
141
shaders/vk14/clouds_noise.comp
Normal file
141
shaders/vk14/clouds_noise.comp
Normal file
@@ -0,0 +1,141 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the tiling noises the clouds and the fog are made of, made once at the start (opengl46_weather::init).
|
||||
// kind 0, the shape (3d): r perlin-worley (perlin fbm with the worley cells carved in), g b a worley fbm of rising frequency;
|
||||
// kind 1, the detail (3d): r g b worley fbm of rising frequency;
|
||||
// kind 2, the coverage (2d): r g b three perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "random.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
layout(rgba8, set = COMPUTE_SET, binding = 0) uniform writeonly image3D noise_volume;
|
||||
layout(rgba8, set = COMPUTE_SET, binding = 1) uniform writeonly image2D noise_plane;
|
||||
|
||||
// the parameters of the kernel (push constants)
|
||||
layout(push_constant) uniform kernel_constants
|
||||
{
|
||||
int kind;
|
||||
int size;
|
||||
};
|
||||
|
||||
vec3 hash3(ivec3 cell, int period, uint seed)
|
||||
{
|
||||
uvec3 wrapped = uvec3((cell % period + period) % period);
|
||||
uint h = Hash(uvec3(wrapped.x + seed * 977U, wrapped.y, wrapped.z));
|
||||
uint h2 = Hash(h + 0x68bc21ebU);
|
||||
uint h3 = Hash(h2 + 0x02e5be93U);
|
||||
return vec3(float(h & 0xffffU), float(h2 & 0xffffU), float(h3 & 0xffffU)) / 65535.0;
|
||||
}
|
||||
|
||||
// gradient noise repeating every Period cells, -1..1 about
|
||||
float perlin(vec3 position, int period, uint seed)
|
||||
{
|
||||
ivec3 cell = ivec3(floor(position));
|
||||
vec3 local = fract(position);
|
||||
vec3 fade = local * local * local * (local * (local * 6.0 - 15.0) + 10.0);
|
||||
float corners[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
|
||||
vec3 gradient = normalize(hash3(cell + offset, period, seed) * 2.0 - 1.0 + 1e-4);
|
||||
corners[i] = dot(gradient, local - vec3(offset));
|
||||
}
|
||||
float x0 = mix(corners[0], corners[1], fade.x);
|
||||
float x1 = mix(corners[2], corners[3], fade.x);
|
||||
float x2 = mix(corners[4], corners[5], fade.x);
|
||||
float x3 = mix(corners[6], corners[7], fade.x);
|
||||
return mix(mix(x0, x1, fade.y), mix(x2, x3, fade.y), fade.z) * 1.6;
|
||||
}
|
||||
|
||||
// inverted distance to the nearest of the points scattered one per cell, 0-1, repeating every Period cells
|
||||
float worley(vec3 position, int period, uint seed)
|
||||
{
|
||||
ivec3 cell = ivec3(floor(position));
|
||||
vec3 local = fract(position);
|
||||
float nearest = 1.0;
|
||||
for (int z = -1; z <= 1; z++)
|
||||
for (int y = -1; y <= 1; y++)
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
ivec3 offset = ivec3(x, y, z);
|
||||
vec3 point = vec3(offset) + hash3(cell + offset, period, seed);
|
||||
nearest = min(nearest, length(point - local));
|
||||
}
|
||||
return 1.0 - clamp(nearest, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float perlin_fbm(vec3 position, int frequency, int octaves, uint seed)
|
||||
{
|
||||
float sum = 0.0;
|
||||
float amplitude = 0.5;
|
||||
float total = 0.0;
|
||||
for (int i = 0; i < octaves; i++)
|
||||
{
|
||||
sum += perlin(position * float(frequency), frequency, seed + uint(i)) * amplitude;
|
||||
total += amplitude;
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2;
|
||||
}
|
||||
return sum / total;
|
||||
}
|
||||
|
||||
float worley_fbm(vec3 position, int frequency, uint seed)
|
||||
{
|
||||
return worley(position * float(frequency), frequency, seed) * 0.625
|
||||
+ worley(position * float(frequency * 2), frequency * 2, seed + 1U) * 0.25
|
||||
+ worley(position * float(frequency * 4), frequency * 4, seed + 2U) * 0.125;
|
||||
}
|
||||
|
||||
// octaves of the coverage noise: it only places the clouds and gives their outline. it is the same at every height, so its finer
|
||||
// octaves would stand as upright grooves down the sides of the clouds; their small shapes come from the 3d noises
|
||||
const int COVERAGE_OCTAVES = 4;
|
||||
|
||||
// the worley fbm keeps about to this range (5th and 95th percentile 0.35 and 0.61)
|
||||
const float WORLEY_FBM_LOW = 0.30;
|
||||
const float WORLEY_FBM_HIGH = 0.65;
|
||||
|
||||
float remap(float value, float low, float high, float newlow, float newhigh)
|
||||
{
|
||||
return newlow + (value - low) / (high - low) * (newhigh - newlow);
|
||||
}
|
||||
|
||||
// a sum of noises is about normally distributed; this spreads it about evenly over 0-1
|
||||
float even(float value, float spread)
|
||||
{
|
||||
return clamp(0.5 + 0.5 * tanh(value / spread * 1.1), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec3 texel = ivec3(gl_GlobalInvocationID);
|
||||
if (any(greaterThanEqual(texel.xy, ivec2(size))))
|
||||
return;
|
||||
if (kind == 2)
|
||||
{
|
||||
vec3 position = vec3((vec2(texel.xy) + 0.5) / float(size), 0.0);
|
||||
float first = perlin_fbm(position, 4, COVERAGE_OCTAVES, 11U);
|
||||
float second = perlin_fbm(position + vec3(0.0, 0.0, 0.5), 8, 5, 23U);
|
||||
float third = perlin_fbm(position + vec3(0.0, 0.0, 0.25), 8, 5, 29U);
|
||||
imageStore(noise_plane, texel.xy, vec4(even(first, 0.22), even(second, 0.22), even(third, 0.22), 1.0));
|
||||
return;
|
||||
}
|
||||
if (texel.z >= size)
|
||||
return;
|
||||
vec3 position = (vec3(texel) + 0.5) / float(size);
|
||||
if (kind == 0)
|
||||
{
|
||||
// both spread over 0-1 first: as sums of octaves they keep to a narrow band about the middle, and a cloud made of them
|
||||
// would be an even block with no lumps. the cells then cut the perlin noise into rounded heaps: whole in their middles,
|
||||
// only the highest perlin left between them
|
||||
float low = even(perlin_fbm(position, 4, 4, 3U), 0.22);
|
||||
float cells = worley_fbm(position, 4, 5U);
|
||||
float spreadcells = clamp((cells - WORLEY_FBM_LOW) / (WORLEY_FBM_HIGH - WORLEY_FBM_LOW), 0.0, 1.0);
|
||||
float perlinworley = clamp(remap(low, (1.0 - spreadcells) * 0.6, 1.0, 0.0, 1.0), 0.0, 1.0);
|
||||
imageStore(noise_volume, texel, vec4(perlinworley, cells, worley_fbm(position, 8, 7U), worley_fbm(position, 16, 9U)));
|
||||
}
|
||||
else
|
||||
imageStore(noise_volume, texel, vec4(worley_fbm(position, 4, 13U), worley_fbm(position, 8, 15U), worley_fbm(position, 16, 17U), 1.0));
|
||||
}
|
||||
34
shaders/vk14/clouds_shadow.comp
Normal file
34
shaders/vk14/clouds_shadow.comp
Normal file
@@ -0,0 +1,34 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the shadow the clouds cast on the ground: for points of the ground reference around the camera, what the clouds let through
|
||||
// towards the key light. the shading (light_common.glsl) and the fog (fog_inject.comp) look it up along the ray of the sun,
|
||||
// see cloud_shadow() in weather_common.glsl
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
#include "clouds_common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
layout(r16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D shadow;
|
||||
|
||||
// the parameters of the kernel (push constants)
|
||||
layout(push_constant) uniform kernel_constants
|
||||
{
|
||||
int size;
|
||||
// world x and z of the centre of the map relative to the camera, size of the map (metres)
|
||||
vec3 placement;
|
||||
// samples through a layer
|
||||
int steps;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (any(greaterThanEqual(texel, ivec2(size))))
|
||||
return;
|
||||
vec2 offset = placement.xy + ((vec2(texel) + 0.5) / float(size) - 0.5) * placement.z;
|
||||
// the ground reference, the earth curving away
|
||||
vec3 ground = vec3(offset.x, -weather_camera.x + dot(offset, offset) * (0.5 / CLOUD_EARTH_RADIUS), offset.y);
|
||||
imageStore(shadow, texel, vec4(cloud_transmittance_to_sun(ground, steps)));
|
||||
}
|
||||
72
shaders/vk14/clouds_temporal.comp
Normal file
72
shaders/vk14/clouds_temporal.comp
Normal file
@@ -0,0 +1,72 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the clouds of the main view smoothed over frames: the image of this frame (clouds_march.comp, ray started at another
|
||||
// distance every frame) blended into the one of the previous frames, found where the clouds were by the distance of the
|
||||
// clouds, the rotation and the move of the camera. the history is held to the range of the neighbourhood of this frame,
|
||||
// so moving clouds and a turning camera don't leave trails
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D current_color;
|
||||
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D current_depth;
|
||||
layout(set = COMPUTE_SET, binding = 2) uniform sampler2D history_color;
|
||||
layout(rgba16f, set = COMPUTE_SET, binding = 3) uniform writeonly image2D result;
|
||||
|
||||
// the parameters of the kernel (push constants)
|
||||
layout(push_constant) uniform kernel_constants
|
||||
{
|
||||
ivec2 size;
|
||||
mat4 inverse_projection;
|
||||
mat4 view_to_world;
|
||||
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
|
||||
mat4 previous_projection_view;
|
||||
// where the camera was in the previous frame, relative to where it is (world, metres)
|
||||
vec3 previous_camera;
|
||||
// share of this frame in the result; 1 when there is no history
|
||||
float blend;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (any(greaterThanEqual(texel, size)))
|
||||
return;
|
||||
vec4 current = texelFetch(current_color, texel, 0);
|
||||
if (blend >= 1.0)
|
||||
{
|
||||
imageStore(result, texel, current);
|
||||
return;
|
||||
}
|
||||
// the range of the neighbourhood
|
||||
vec4 low = current;
|
||||
vec4 high = current;
|
||||
for (int y = -1; y <= 1; y++)
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
vec4 neighbour = texelFetch(current_color, clamp(texel + ivec2(x, y), ivec2(0), size - 1), 0);
|
||||
low = min(low, neighbour);
|
||||
high = max(high, neighbour);
|
||||
}
|
||||
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
|
||||
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
|
||||
vec3 direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
|
||||
float span_distance = texelFetch(current_depth, texel, 0).r;
|
||||
vec3 position = direction * span_distance - previous_camera;
|
||||
vec4 clip = previous_projection_view * vec4(position, 1.0);
|
||||
float share = blend;
|
||||
vec4 history = current;
|
||||
if (clip.w > 0.0)
|
||||
{
|
||||
vec2 coords = clip.xy / clip.w * 0.5 + 0.5;
|
||||
if (all(greaterThanEqual(coords, vec2(0.0))) && all(lessThanEqual(coords, vec2(1.0))))
|
||||
history = clamp(textureLod(history_color, coords, 0.0), low, high);
|
||||
else
|
||||
share = 1.0;
|
||||
}
|
||||
else
|
||||
share = 1.0;
|
||||
imageStore(result, texel, mix(history, current, share));
|
||||
}
|
||||
48
shaders/vk14/cluster_heatmap.frag
Normal file
48
shaders/vk14/cluster_heatmap.frag
Normal file
@@ -0,0 +1,48 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
#include "common.glsl"
|
||||
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
|
||||
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
|
||||
// drawn over the output of the viewport, with the layout of the scene (the clusters of the pass set; the depth as the texture of
|
||||
// a material)
|
||||
|
||||
layout(location = 14) in vec2 f_coords;
|
||||
|
||||
#include "common.glsl"
|
||||
|
||||
#pragma texture(depth_tex, 0, R)
|
||||
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
const float TINT = 0.55;
|
||||
|
||||
// blue (few) through green to red (many)
|
||||
vec3 ramp(float t)
|
||||
{
|
||||
return clamp(vec3(1.5) - abs(4.0 * t - vec3(3.0, 2.0, 1.0)), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// reverse z: 0 is the far plane at infinity, i.e. the sky, which has no cluster
|
||||
float depth = texelFetch(depth_tex, ivec2(f_coords * vec2(textureSize(depth_tex, 0))), 0).r;
|
||||
if (depth <= 0.0)
|
||||
discard;
|
||||
|
||||
// depth is znear / w
|
||||
float w = cluster_znear / depth;
|
||||
int slice = clamp(int(floor(log(w) * cluster_slice_scale + cluster_slice_bias)), 0, CLUSTER_GRID_Z - 1);
|
||||
ivec2 tile = clamp(ivec2(floor(f_coords * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y))), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
|
||||
|
||||
uint count = cluster_ranges[tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)].count;
|
||||
if (count == 0U)
|
||||
discard;
|
||||
|
||||
// a full cluster (lights beyond its capacity are dropped) is white; the rest goes by the logarithm of the count
|
||||
vec3 color = (count >= uint(MAX_LIGHTS_PER_CLUSTER))
|
||||
? vec3(1.0)
|
||||
: ramp(log2(1.0 + float(count)) / log2(1.0 + float(MAX_LIGHTS_PER_CLUSTER)));
|
||||
|
||||
out_color = vec4(color, TINT);
|
||||
}
|
||||
@@ -6,10 +6,14 @@
|
||||
//
|
||||
// the pipeline layout is picked by a define before the include of this file:
|
||||
// (none) the passes of the scene (materials, shadows, picking, lines, sprites, sky, particles): set PASS_SET with
|
||||
// the blocks of the scene, of the draws, of the instances, the lights, the clusters and the shadow,
|
||||
// environment and headlight maps; set MATERIAL_SET with the textures of the material
|
||||
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read, the
|
||||
// parameters in the push constants (param stands for pc.data)
|
||||
// the blocks of the scene, of the draws, of the instances, the lights, the clusters, the shadow,
|
||||
// environment and headlight maps, the light of the sky, the weather, the shadows of the lamps and the depth
|
||||
// of the scene; set MATERIAL_SET with the textures of the material
|
||||
// LAYOUT_COMPUTE the compute kernels (vk14::compute_kernel): set PASS_SET as above (the block of the scene, the lights, the
|
||||
// clusters, the light of the sky; the weather and the atmosphere with their includes), set COMPUTE_SET
|
||||
// pushed with the bindings the kernel declares from 0 on, its parameters in push constants of its own
|
||||
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read and the
|
||||
// exposure, the parameters in the push constants (param stands for pc.data)
|
||||
// LAYOUT_CLUSTER the compute passes of the light clusters: set 0 pushed at the CL_* bindings
|
||||
// LAYOUT_UI the user interface: none of the above, its own push constants
|
||||
//
|
||||
@@ -40,7 +44,22 @@
|
||||
#define SHADOW_TEX 7
|
||||
#define ENV_TEX 8
|
||||
#define HEADLIGHT_TEX 9
|
||||
#define PASS_BINDING_COUNT 10
|
||||
#define SKY_SSBO 10
|
||||
#define ATMOSPHERE_UBO 11
|
||||
#define SKY_VIEW_TEX 12
|
||||
#define ENV_GGX_TEX 13
|
||||
#define WEATHER_UBO 14
|
||||
#define WEATHER_HORIZON_UBO 15
|
||||
#define FOG_VOLUME_TEX 16
|
||||
#define CLOUD_SHADOW_TEX 17
|
||||
#define CLOUD_SHAPE_TEX 18
|
||||
#define CLOUD_DETAIL_TEX 19
|
||||
#define CLOUD_COVERAGE_TEX 20
|
||||
#define LOCAL_SHADOW_TEX 21
|
||||
#define LOCAL_SHADOW_LAMPS_TEX 22
|
||||
#define HEADLIGHT_SHADOW_TEX 23
|
||||
#define SCENE_DEPTH_TEX 24
|
||||
#define PASS_BINDING_COUNT 25
|
||||
#define MATERIAL_TEXTURE_FIRST 0
|
||||
#define CL_CLUSTER_UBO 0
|
||||
#define CL_AABB_SSBO 1
|
||||
@@ -50,6 +69,9 @@
|
||||
#define CL_LIGHT_SSBO 5
|
||||
#define POST_SET 0
|
||||
#define POST_TEXTURE_FIRST 0
|
||||
#define POST_TEXTURES 4
|
||||
#define POST_EXPOSURE_SSBO 4
|
||||
#define COMPUTE_SET 1
|
||||
#define PUSH_CONSTANTS_SIZE 112
|
||||
#define SPEC_OUTPUT_GAMMA 0
|
||||
#define SPEC_SHADOWMAP_ENABLED 1
|
||||
@@ -69,11 +91,35 @@
|
||||
#define MAX_LIGHTS_PER_CLUSTER 64
|
||||
#define CLUSTER_BUILD_GROUP_SIZE 64
|
||||
#define CLUSTER_CULL_GROUP_SIZE 64
|
||||
#define ENVMAP_GGX_LEVELS 7U
|
||||
#define ENVMAP_GGX_ROUGHNESS_MIN 0.200000
|
||||
#define ENVMAP_GGX_GROUP_SIZE 8
|
||||
#define EXPOSURE_HISTOGRAM_BINS 256
|
||||
#define EXPOSURE_HISTOGRAM_GROUP_SIZE 16
|
||||
#define ATMOSPHERE_TRANSMITTANCE_WIDTH 256
|
||||
#define ATMOSPHERE_TRANSMITTANCE_HEIGHT 64
|
||||
#define ATMOSPHERE_MULTISCATTERING_SIZE 32
|
||||
#define ATMOSPHERE_SKYVIEW_WIDTH 192
|
||||
#define ATMOSPHERE_SKYVIEW_HEIGHT 108
|
||||
#define ATMOSPHERE_AMBIENT_GROUP_SIZE 256
|
||||
#define MAX_CLOUD_LAYERS 4
|
||||
#define FOG_HORIZON_SAMPLES 32
|
||||
#define WEATHER_GROUP_SIZE 8
|
||||
#define FOG_VOLUME_BIAS 10.000000
|
||||
#define INSTANCE_CULL_GROUP_SIZE 64
|
||||
#define INSTANCE_OFFSETS_GROUP_SIZE 1024
|
||||
#define INSTANCE_COMMAND_GROUP_SIZE 64
|
||||
#define INSTANCE_COUNTER_HEADER 8
|
||||
#define INSTANCE_JOB_SLEEPERS 1U
|
||||
#define HIZ_GROUP_SIZE 8
|
||||
#define SHADOW_FIT_GROUP_SIZE 16
|
||||
#define LOCAL_SHADOW_NEAR 0.150000
|
||||
#define PARTICLE_GROUP_SIZE 64
|
||||
#endif
|
||||
|
||||
#include "settings.glsl"
|
||||
|
||||
#ifndef LAYOUT_UI
|
||||
#if !defined(LAYOUT_UI) && !defined(LAYOUT_COMPUTE)
|
||||
// vk14::draw_constants, PUSH_CONSTANTS_SIZE bytes, visible in every stage. draw: the element of model_draws[] the draw reads
|
||||
// (the passes of the scene); flags and data: the parameters of the special shaders, each says what it takes from them
|
||||
layout(push_constant) uniform draw_constants_block
|
||||
@@ -171,8 +217,9 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
|
||||
uint lights_count;
|
||||
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
|
||||
uint lights_clustered;
|
||||
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
|
||||
// scale of the directional part of the sky ambient light, see sky_ssbo
|
||||
float sky_ambient_factor;
|
||||
// 1 if rough reflections come from the prefiltered environment map
|
||||
uint env_ggx;
|
||||
|
||||
light_s lights[];
|
||||
@@ -183,6 +230,16 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
|
||||
// the post-processing reads the parameters the opengl renderer gives it in the model block from the push constants
|
||||
#define param pc.data
|
||||
|
||||
// exposure worked out by exposure_average.comp, gl::exposure_data
|
||||
layout(std430, set = POST_SET, binding = POST_EXPOSURE_SSBO) readonly buffer exposure_ssbo
|
||||
{
|
||||
uint histogram[EXPOSURE_HISTOGRAM_BINS];
|
||||
float adapted_luminance;
|
||||
float exposure;
|
||||
float average_luminance;
|
||||
uint valid;
|
||||
};
|
||||
|
||||
#elif !defined(LAYOUT_UI)
|
||||
|
||||
// the block of the scene, gl::scene_ubs (every member kept, the layout is the one of the c++ struct)
|
||||
@@ -204,7 +261,8 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
|
||||
vec4 shadow_fit;
|
||||
};
|
||||
|
||||
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw. the names below stand for its
|
||||
#ifndef LAYOUT_COMPUTE
|
||||
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw (MODEL_DRAW). the names below stand for its
|
||||
// fields, so the shaders read them as the uniform block of the opengl renderer
|
||||
struct model_draw_s
|
||||
{
|
||||
@@ -225,15 +283,32 @@ layout(std430, set = PASS_SET, binding = DRAW_SSBO) readonly buffer model_draw_s
|
||||
model_draw_s model_draws[];
|
||||
};
|
||||
|
||||
#define modelview model_draws[pc.draw].modelview
|
||||
#define modelviewnormal model_draws[pc.draw].modelviewnormal
|
||||
#define param model_draws[pc.draw].param
|
||||
#define future model_draws[pc.draw].future
|
||||
#define opacity model_draws[pc.draw].opacity
|
||||
#define emission model_draws[pc.draw].emission
|
||||
#define fog_density model_draws[pc.draw].fog_density
|
||||
#define alpha_mult model_draws[pc.draw].alpha_mult
|
||||
#define shadow_tone model_draws[pc.draw].shadow_tone
|
||||
#ifdef MODEL_DRAWS
|
||||
// programs of the multi draws of the instances drawn from the gpu culling: the blocks of the draws of a multi draw follow one
|
||||
// another from the one of pc.draw, picked by the number of the draw in it. the fragment stage gets the block from the vertex
|
||||
// stage (which writes f_model_draw)
|
||||
#if defined(STAGE_VERTEX)
|
||||
layout(location = 11) flat out uint f_model_draw;
|
||||
#define MODEL_DRAW (pc.draw + uint(gl_DrawID))
|
||||
#elif defined(STAGE_FRAGMENT)
|
||||
layout(location = 11) flat in uint f_model_draw;
|
||||
#define MODEL_DRAW f_model_draw
|
||||
#else
|
||||
#define MODEL_DRAW pc.draw
|
||||
#endif
|
||||
#else
|
||||
#define MODEL_DRAW pc.draw
|
||||
#endif
|
||||
|
||||
#define modelview model_draws[MODEL_DRAW].modelview
|
||||
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
|
||||
#define param model_draws[MODEL_DRAW].param
|
||||
#define future model_draws[MODEL_DRAW].future
|
||||
#define opacity model_draws[MODEL_DRAW].opacity
|
||||
#define emission model_draws[MODEL_DRAW].emission
|
||||
#define fog_density model_draws[MODEL_DRAW].fog_density
|
||||
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
|
||||
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
|
||||
|
||||
// per-instance modelview matrices of the instanced draws, element 0 the identity. a draw which isn't instanced is drawn with
|
||||
// firstInstance 0, an instanced one from its base instance on, with modelview set to the submodel-local chain (computed from
|
||||
@@ -242,6 +317,7 @@ layout(std430, set = PASS_SET, binding = INSTANCE_SSBO) readonly buffer instance
|
||||
{
|
||||
mat4 instance_modelview[];
|
||||
};
|
||||
#endif
|
||||
|
||||
// the light buffer of the pass, gl::light_data
|
||||
layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
|
||||
@@ -255,13 +331,21 @@ layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
|
||||
uint lights_count;
|
||||
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
|
||||
uint lights_clustered;
|
||||
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
|
||||
// scale of the directional part of the sky ambient light, see sky_ssbo
|
||||
float sky_ambient_factor;
|
||||
// 1 if rough reflections come from the prefiltered environment map
|
||||
uint env_ggx;
|
||||
|
||||
light_s lights[];
|
||||
};
|
||||
|
||||
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
|
||||
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
|
||||
layout(std430, set = PASS_SET, binding = SKY_SSBO) readonly buffer sky_ssbo
|
||||
{
|
||||
vec4 sky_ambient_sh[9];
|
||||
};
|
||||
|
||||
// the grid of the light clusters of the pass, gl::cluster_ubs; the compute passes fill the lists, shading only reads them
|
||||
layout(std140, set = PASS_SET, binding = CLUSTER_UBO) uniform cluster_ubo
|
||||
{
|
||||
|
||||
10
shaders/vk14/depthprepass.frag
Normal file
10
shaders/vk14/depthprepass.frag
Normal file
@@ -0,0 +1,10 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
#include "common.glsl"
|
||||
// depth prepass of materials which never discard a fragment (no alpha test)
|
||||
|
||||
layout(location = 2) in vec2 f_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
}
|
||||
26
shaders/vk14/depthprepass.vert
Normal file
26
shaders/vk14/depthprepass.vert
Normal file
@@ -0,0 +1,26 @@
|
||||
#version 460 core
|
||||
#define STAGE_VERTEX 1
|
||||
#include "common.glsl"
|
||||
// depth prepass of the instances drawn from the gpu culling (opengl46_renderer::Render_Instances_Indirect()).
|
||||
// the position is worked out the way vertex.vert of the materials does it, and declared invariant in both, so the colour
|
||||
// pass which follows gets the same depth for the same surface
|
||||
|
||||
layout(location = 0) in vec3 v_vert;
|
||||
layout(location = 2) in vec2 v_coord;
|
||||
|
||||
|
||||
invariant gl_Position;
|
||||
|
||||
layout(location = 2) out vec2 f_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
mat4 mv = effective_modelview();
|
||||
vec4 position = mv * vec4(v_vert, 1.0);
|
||||
gl_Position = projection * position;
|
||||
f_coord = v_coord;
|
||||
#ifdef MODEL_DRAWS
|
||||
// which block of the multi draw the fragments read (common.glsl)
|
||||
f_model_draw = MODEL_DRAW;
|
||||
#endif
|
||||
}
|
||||
18
shaders/vk14/depthprepass_alpha.frag
Normal file
18
shaders/vk14/depthprepass_alpha.frag
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
#include "common.glsl"
|
||||
// depth prepass of materials whose colour shader tests the alpha of the diffuse texture against the opacity of the
|
||||
// material (mat_default.frag and the like). the test here is stricter by one step of 8-bit alpha: a fragment which writes
|
||||
// depth here is never discarded by the colour pass, which would leave a hole with nothing drawn behind it
|
||||
|
||||
layout(location = 2) in vec2 f_coord;
|
||||
|
||||
|
||||
#pragma texture(diffuse, 0, sRGB_A)
|
||||
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (texture(diffuse, f_coord).a < opacity + 1.0 / 255.0)
|
||||
discard;
|
||||
}
|
||||
107
shaders/vk14/envmap_prefilter.comp
Normal file
107
shaders/vk14/envmap_prefilter.comp
Normal file
@@ -0,0 +1,107 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level
|
||||
// of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap
|
||||
// of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a
|
||||
// smooth result. run when an update of the environment map is complete
|
||||
|
||||
#define LAYOUT_COMPUTE 1
|
||||
#include "common.glsl"
|
||||
|
||||
layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in;
|
||||
|
||||
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform imageCube prefilter_target;
|
||||
// the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to
|
||||
layout(set = COMPUTE_SET, binding = 1) uniform samplerCube envmap;
|
||||
|
||||
// the parameters of the kernel (push constants)
|
||||
layout(push_constant) uniform kernel_constants
|
||||
{
|
||||
// perceptual roughness of the level being written
|
||||
float roughness;
|
||||
// size of the level being written, and of the first level of the source
|
||||
int target_size;
|
||||
float source_size;
|
||||
int sample_count;
|
||||
};
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
|
||||
// direction through the centre of a texel of a cube map face, in the order and orientation of GL_TEXTURE_CUBE_MAP_POSITIVE_X..
|
||||
vec3 cube_direction(ivec3 texel, int size)
|
||||
{
|
||||
vec2 uv = (vec2(texel.xy) + 0.5) / float(size) * 2.0 - 1.0;
|
||||
switch (texel.z)
|
||||
{
|
||||
case 0: return vec3(1.0, -uv.y, -uv.x);
|
||||
case 1: return vec3(-1.0, -uv.y, uv.x);
|
||||
case 2: return vec3(uv.x, 1.0, uv.y);
|
||||
case 3: return vec3(uv.x, -1.0, -uv.y);
|
||||
case 4: return vec3(uv.x, -uv.y, 1.0);
|
||||
default: return vec3(-uv.x, -uv.y, -1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 hammersley(uint i, uint n)
|
||||
{
|
||||
uint bits = bitfieldReverse(i);
|
||||
return vec2(float(i) / float(n), float(bits) * 2.3283064365386963e-10);
|
||||
}
|
||||
|
||||
// half vector around N for the GGX distribution of linear roughness a
|
||||
vec3 importance_sample_ggx(vec2 xi, float a, vec3 N)
|
||||
{
|
||||
float phi = 2.0 * PI * xi.x;
|
||||
float costheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
|
||||
float sintheta = sqrt(max(1.0 - costheta * costheta, 0.0));
|
||||
vec3 H = vec3(sintheta * cos(phi), sintheta * sin(phi), costheta);
|
||||
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 tangentx = normalize(cross(up, N));
|
||||
vec3 tangenty = cross(N, tangentx);
|
||||
return tangentx * H.x + tangenty * H.y + N * H.z;
|
||||
}
|
||||
|
||||
float distribution_ggx(float NdotH, float a)
|
||||
{
|
||||
float a2 = a * a;
|
||||
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||
return a2 / (PI * d * d);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec3 texel = ivec3(gl_GlobalInvocationID);
|
||||
if (texel.x >= target_size || texel.y >= target_size)
|
||||
return;
|
||||
|
||||
vec3 N = normalize(cube_direction(texel, target_size));
|
||||
// the view is taken along the normal, which is what the split sum approximation assumes
|
||||
vec3 V = N;
|
||||
float a = max(roughness * roughness, 1e-3);
|
||||
float texelsolidangle = 4.0 * PI / (6.0 * source_size * source_size);
|
||||
|
||||
vec3 sum = vec3(0.0);
|
||||
float weight = 0.0;
|
||||
uint count = uint(sample_count);
|
||||
for (uint i = 0U; i < count; ++i)
|
||||
{
|
||||
vec3 H = importance_sample_ggx(hammersley(i, count), a, N);
|
||||
float VdotH = dot(V, H);
|
||||
vec3 L = 2.0 * VdotH * H - V;
|
||||
float NdotL = dot(N, L);
|
||||
if (NdotL <= 0.0)
|
||||
continue;
|
||||
float NdotH = max(dot(N, H), 0.0);
|
||||
// pdf of L: D * NdotH / (4 * VdotH)
|
||||
float pdf = distribution_ggx(NdotH, a) * NdotH / max(4.0 * VdotH, 1e-4);
|
||||
float samplesolidangle = 1.0 / (float(count) * max(pdf, 1e-6));
|
||||
float level = max(0.5 * log2(samplesolidangle / texelsolidangle) + 1.0, 0.0);
|
||||
vec3 color = textureLod(envmap, L, level).rgb;
|
||||
if (any(isnan(color)) || any(isinf(color)))
|
||||
color = vec3(0.0);
|
||||
sum += max(color, vec3(0.0)) * NdotL;
|
||||
weight += NdotL;
|
||||
}
|
||||
|
||||
imageStore(prefilter_target, texel, vec4(sum / max(weight, 1e-4), 1.0));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user