17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 18:38:36 +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:
2026-10-25 03:06:01 +01:00
parent 49f9df7d98
commit c48c58776a
146 changed files with 20748 additions and 4606 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -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
{

View File

@@ -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)

View File

@@ -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
};

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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()

View File

@@ -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};

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -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]{};
};