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:
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
#include "rendering/common/exposure.h"
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
@@ -17,16 +18,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
|
||||
float const HISTOGRAM_LOG_MIN{-12.f};
|
||||
float const HISTOGRAM_LOG_MAX{6.f};
|
||||
// the bloom chain goes down this many levels at most, and not below this size
|
||||
std::size_t const BLOOM_LEVELS{6};
|
||||
int const BLOOM_MIN_SIZE{8};
|
||||
// format of the levels: no alpha, no negative values, half the memory of rgba16f
|
||||
GLint const BLOOM_FORMAT{GL_R11F_G11F_B10F};
|
||||
// soft knee of the threshold, a fraction of it
|
||||
float const BLOOM_KNEE{0.5f};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -58,12 +51,8 @@ void opengl46_exposure::bind()
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> opengl46_exposure::make_bloom_chain(int const Width, int const Height)
|
||||
{
|
||||
auto chain{std::make_unique<bloom_chain>()};
|
||||
glm::ivec2 size{Width, Height};
|
||||
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
|
||||
for (auto const &size : gfx::exposure_settings::bloom_sizes(Width, Height))
|
||||
{
|
||||
size = glm::max(size / 2, glm::ivec2{1});
|
||||
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
|
||||
break;
|
||||
chain->sizes.push_back(size);
|
||||
auto &texture{chain->down.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
@@ -94,19 +83,18 @@ void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
|
||||
m_exposure_timer.begin();
|
||||
m_exposure_ssbo->bind_base();
|
||||
Image.bind(0);
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
glProgramUniform2f(*m_histogram_program, 0, HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN));
|
||||
auto const histogramrange{gfx::exposure_settings::histogram_range()};
|
||||
glProgramUniform2f(*m_histogram_program, 0, histogramrange.x, histogramrange.y);
|
||||
auto const groupsize{2 * static_cast<int>(gl::EXPOSURE_HISTOGRAM_GROUP_SIZE)};
|
||||
m_histogram_program->dispatch((Image.get_width() + groupsize - 1) / groupsize, (Image.get_height() + groupsize - 1) / groupsize);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
glProgramUniform2f(*m_average_program, 0, HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins);
|
||||
glProgramUniform4f(*m_average_program, 1,
|
||||
static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)),
|
||||
Global.gfx_postfx_autoexposure_speed_dark,
|
||||
Global.gfx_postfx_autoexposure_speed_bright,
|
||||
Global.gfx_postfx_autoexposure_key);
|
||||
glProgramUniform2f(*m_average_program, 2, std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max));
|
||||
auto const averagerange{gfx::exposure_settings::average_range()};
|
||||
auto const adaptation{gfx::exposure_settings::adaptation(Deltatime)};
|
||||
auto const exposurerange{gfx::exposure_settings::exposure_range()};
|
||||
glProgramUniform2f(*m_average_program, 0, averagerange.x, averagerange.y);
|
||||
glProgramUniform4f(*m_average_program, 1, adaptation.x, adaptation.y, adaptation.z, adaptation.w);
|
||||
glProgramUniform2f(*m_average_program, 2, exposurerange.x, exposurerange.y);
|
||||
m_average_program->dispatch(1);
|
||||
// read by the tonemapping, and copied for the debug window
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
@@ -122,7 +110,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
|
||||
// down, the first step from the image itself
|
||||
for (std::size_t level = 0; level < Chain.sizes.size(); ++level)
|
||||
{
|
||||
Model.param[0] = glm::vec4{(level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
|
||||
Model.param[0] = gfx::exposure_settings::bloom_params(level);
|
||||
Modelubo.update(Model);
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_down->apply(level == 0 ? Image : *Chain.down[level - 1], Chain.down_fb[level].get());
|
||||
@@ -141,12 +129,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
|
||||
|
||||
glm::vec4 opengl46_exposure::tonemap_params(std::size_t const Bloomlevels) const
|
||||
{
|
||||
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
|
||||
return glm::vec4{
|
||||
bloom,
|
||||
Global.gfx_postfx_autoexposure ? 1.f : 0.f,
|
||||
std::exp2(Global.gfx_postfx_autoexposure_compensation),
|
||||
Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
|
||||
return gfx::exposure_settings::tonemap_params(Bloomlevels);
|
||||
}
|
||||
|
||||
std::string opengl46_exposure::info_times()
|
||||
@@ -161,15 +144,5 @@ std::string opengl46_exposure::info_times()
|
||||
|
||||
void opengl46_exposure::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
|
||||
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
|
||||
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
|
||||
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
|
||||
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", m_readback_data[2], m_readback_data[0], m_readback_data[1], std::log2(std::max(m_readback_data[1], 1e-6f)));
|
||||
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
|
||||
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
|
||||
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
|
||||
gfx::exposure_settings::draw_debug_ui(m_readback_data);
|
||||
}
|
||||
|
||||
@@ -10,46 +10,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46gpuparticles.h"
|
||||
|
||||
#include "rendering/particles.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "gl/vao.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
|
||||
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
|
||||
// the origin of the smoke moves by whole steps when the camera gets this far from it
|
||||
double constexpr ORIGIN_STEP{256.0};
|
||||
double constexpr ORIGIN_REACH{1024.0};
|
||||
// how much the smoke scatters the light onwards (Henyey-Greenstein)
|
||||
float constexpr SMOKE_FORWARD{0.3f};
|
||||
|
||||
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
|
||||
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
|
||||
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
|
||||
|
||||
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
|
||||
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
|
||||
struct precipitation_kind
|
||||
{
|
||||
float fall;
|
||||
float exposure;
|
||||
float opacity;
|
||||
float forward;
|
||||
float brightness;
|
||||
};
|
||||
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
|
||||
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
|
||||
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
|
||||
double constexpr WIND_SHARE{0.5};
|
||||
// the camera faster than this from a frame to the next was moved, not driven or flown
|
||||
double constexpr CAMERA_JUMP{120.0};
|
||||
|
||||
std::unique_ptr<gl::program> make_program(std::vector<std::string> const &Names)
|
||||
{
|
||||
std::vector<std::unique_ptr<gl::shader>> shaders;
|
||||
@@ -76,11 +43,8 @@ opengl46_gpu_particles::~opengl46_gpu_particles()
|
||||
::glDeleteVertexArrays(1, &m_vao);
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::init()
|
||||
bool opengl46_gpu_particles::gpu_init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
try
|
||||
{
|
||||
m_smokesimulate = make_program({"particles_smoke_simulate.comp"});
|
||||
@@ -109,88 +73,26 @@ bool opengl46_gpu_particles::init()
|
||||
m_precipitationcommand = std::make_unique<gl::ssbo>(4 * sizeof(GLuint), gl::PARTICLE_COMMAND_SSBO_BINDING);
|
||||
m_smokereadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_precipitationreadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::smoke_available()
|
||||
void opengl46_gpu_particles::gpu_clear()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
bool opengl46_gpu_particles::precipitation_available()
|
||||
{
|
||||
return init();
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::clear()
|
||||
{
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_poolfull = false;
|
||||
m_sourcerecords.clear();
|
||||
m_places = 0;
|
||||
m_particles.reset();
|
||||
m_sources.reset();
|
||||
m_owners.reset();
|
||||
m_smokeoutput.reset();
|
||||
m_precipitationoutput.reset();
|
||||
m_precipitationcapacity = 0;
|
||||
m_originset = false;
|
||||
m_lastupdate = 0;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::layout(particle_manager &Particles)
|
||||
void opengl46_gpu_particles::gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources)
|
||||
{
|
||||
auto const &sources{Particles.sequence()};
|
||||
// the sources already laid out which asked for the same places since
|
||||
std::size_t kept{0};
|
||||
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
|
||||
++kept;
|
||||
if ((kept == m_requested.size()) && (kept == sources.size()) && m_particles)
|
||||
return;
|
||||
if (kept < m_requested.size())
|
||||
{
|
||||
// a source went away or changed: all anew
|
||||
kept = 0;
|
||||
m_pool.clear();
|
||||
m_requested.clear();
|
||||
m_places = 0;
|
||||
m_poolfull = false;
|
||||
}
|
||||
auto const oldplaces{m_places};
|
||||
|
||||
auto first{m_places};
|
||||
m_pool.resize(sources.size());
|
||||
m_requested.resize(sources.size());
|
||||
for (std::size_t i = kept; i < sources.size(); ++i)
|
||||
{
|
||||
m_requested[i] = sources[i].max_particles();
|
||||
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
|
||||
m_pool[i] = pool_source{};
|
||||
if (first + size > MAX_SMOKE_PLACES)
|
||||
{
|
||||
if (false == m_poolfull)
|
||||
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
|
||||
m_poolfull = true;
|
||||
continue;
|
||||
}
|
||||
m_pool[i].first = first;
|
||||
m_pool[i].size = size;
|
||||
first += size;
|
||||
}
|
||||
m_places = first;
|
||||
|
||||
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
|
||||
for (std::size_t i = 0; i < m_pool.size(); ++i)
|
||||
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
|
||||
|
||||
auto const places{static_cast<GLsizeiptr>(std::max<std::uint32_t>(m_places, 1))};
|
||||
auto const places{static_cast<GLsizeiptr>(Places)};
|
||||
// the particles of the places kept go over to the new storage, the new places are dead
|
||||
auto particles{std::make_unique<gl::ssbo>(places * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)), gl::PARTICLE_SSBO_BINDING)};
|
||||
::glClearNamedBufferData(*particles, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
if (m_particles && (oldplaces > 0) && (kept > 0))
|
||||
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(oldplaces) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
|
||||
if (m_particles && (Kept > 0))
|
||||
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(Kept) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
|
||||
m_particles = std::move(particles);
|
||||
|
||||
auto const ensure = [](std::unique_ptr<gl::ssbo> &Buffer, GLsizeiptr const Size, GLuint const Binding) {
|
||||
@@ -201,96 +103,29 @@ void opengl46_gpu_particles::layout(particle_manager &Particles)
|
||||
};
|
||||
ensure(m_owners, places * static_cast<GLsizeiptr>(sizeof(std::uint32_t)), gl::PARTICLE_OWNER_SSBO_BINDING);
|
||||
ensure(m_smokeoutput, places * static_cast<GLsizeiptr>(sizeof(gl::particle_draw)), gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(sources.size(), 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
|
||||
m_owners->update(owners.data(), 0, static_cast<GLsizeiptr>(owners.size() * sizeof(std::uint32_t)));
|
||||
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
|
||||
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(Sources, 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
|
||||
m_owners->update(Owners.data(), 0, static_cast<GLsizeiptr>(Owners.size() * sizeof(std::uint32_t)));
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
|
||||
void opengl46_gpu_particles::gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records)
|
||||
{
|
||||
if (false == init())
|
||||
return;
|
||||
layout(Particles);
|
||||
if (Particles.updates() == m_lastupdate)
|
||||
return;
|
||||
m_lastupdate = Particles.updates();
|
||||
if (m_places == 0)
|
||||
return;
|
||||
|
||||
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
|
||||
glm::vec3 shift{0.f};
|
||||
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
|
||||
if (false == m_originset)
|
||||
{
|
||||
m_origin = snapped;
|
||||
m_originset = true;
|
||||
}
|
||||
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
|
||||
{
|
||||
shift = glm::vec3{m_origin - snapped};
|
||||
m_origin = snapped;
|
||||
}
|
||||
|
||||
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
|
||||
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
|
||||
auto const &sources{Particles.sequence()};
|
||||
m_smokealive = 0;
|
||||
for (std::size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
auto const &state{sources[i].gpu()};
|
||||
auto &pool{m_pool[i]};
|
||||
auto &record{m_sourcerecords[i]};
|
||||
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
|
||||
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
|
||||
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
|
||||
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
|
||||
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
|
||||
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
|
||||
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], state.spread};
|
||||
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
|
||||
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
|
||||
record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, state.fadein};
|
||||
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
|
||||
if (pool.size > 0)
|
||||
{
|
||||
pool.head = (pool.head + spawns) % pool.size;
|
||||
if (spawns > 0)
|
||||
m_smokealive += pool.size;
|
||||
}
|
||||
}
|
||||
if (false == m_sourcerecords.empty())
|
||||
m_sources->update(m_sourcerecords.data(), 0, static_cast<GLsizeiptr>(m_sourcerecords.size() * sizeof(gl::particle_smoke_source)));
|
||||
if (false == Records.empty())
|
||||
m_sources->update(Records.data(), 0, static_cast<GLsizeiptr>(Records.size() * sizeof(gl::particle_smoke_source)));
|
||||
|
||||
m_simulatetimer.begin();
|
||||
m_particles->bind_base();
|
||||
m_sources->bind_base();
|
||||
m_owners->bind_base();
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto &program{*m_smokesimulate};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform4f(program, 1, static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z);
|
||||
::glProgramUniform4f(program, 2, wind.x, wind.y, wind.z, 0.f);
|
||||
::glProgramUniform1ui(program, 3, static_cast<GLuint>(m_lastupdate));
|
||||
program.dispatch(groups(m_places));
|
||||
::glProgramUniform1ui(program, 0, Simulation.places);
|
||||
::glProgramUniform4fv(program, 1, 1, glm::value_ptr(Simulation.step));
|
||||
::glProgramUniform4fv(program, 2, 1, glm::value_ptr(Simulation.wind));
|
||||
::glProgramUniform1ui(program, 3, static_cast<GLuint>(Simulation.seed));
|
||||
program.dispatch(groups(Simulation.places));
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_simulatetimer.end();
|
||||
}
|
||||
|
||||
std::array<glm::vec4, 5> opengl46_gpu_particles::planes(view const &View)
|
||||
{
|
||||
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
|
||||
auto const matrix{glm::transpose(View.projection * View.modelview)};
|
||||
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
|
||||
for (auto &plane : result)
|
||||
{
|
||||
auto const length{glm::length(glm::vec3{plane})};
|
||||
if (length > 0.f)
|
||||
plane /= length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::collect(gl::readback &Readback, std::size_t &Drawn)
|
||||
{
|
||||
GLuint command[4];
|
||||
@@ -317,11 +152,9 @@ void opengl46_gpu_particles::draw(GLuint const Output, GLuint const Command, gl:
|
||||
::glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_smoke(view const &View)
|
||||
void opengl46_gpu_particles::gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn)
|
||||
{
|
||||
if ((false == init()) || (m_places == 0))
|
||||
return 0;
|
||||
collect(*m_smokereadback, m_smokedrawn);
|
||||
collect(*m_smokereadback, Drawn);
|
||||
|
||||
m_smoketimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
@@ -332,125 +165,60 @@ std::size_t opengl46_gpu_particles::render_smoke(view const &View)
|
||||
m_smokeoutput->bind_base();
|
||||
m_smokecommand->bind_base();
|
||||
auto &program{*m_smokeprepare};
|
||||
auto const origin{glm::vec3{m_origin - View.camera}};
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform1ui(program, 0, m_places);
|
||||
::glProgramUniform3f(program, 1, origin.x, origin.y, origin.z);
|
||||
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 3, Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f);
|
||||
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(m_places));
|
||||
::glProgramUniform1ui(program, 0, Draw.places);
|
||||
::glProgramUniform3fv(program, 1, 1, glm::value_ptr(Draw.origin));
|
||||
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(Draw.view));
|
||||
::glProgramUniform4fv(program, 3, 1, glm::value_ptr(Draw.options));
|
||||
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(Draw.planes[0]));
|
||||
program.dispatch(groups(Draw.places));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
::glProgramUniform4f(*m_smokedraw, 0, View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4fv(*m_smokedraw, 0, 1, glm::value_ptr(Draw.soft));
|
||||
draw(*m_smokeoutput, *m_smokecommand, *m_smokedraw);
|
||||
m_smoketimer.end();
|
||||
|
||||
m_smokereadback->push(*m_smokecommand);
|
||||
return m_smokedrawn;
|
||||
}
|
||||
|
||||
std::size_t opengl46_gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
|
||||
void opengl46_gpu_particles::gpu_collect_precipitation(std::size_t &Drawn)
|
||||
{
|
||||
if (false == init())
|
||||
return 0;
|
||||
collect(*m_precipitationreadback, m_precipitationdrawn);
|
||||
collect(*m_precipitationreadback, Drawn);
|
||||
}
|
||||
|
||||
auto const &kind{Snow ? SNOW : RAIN};
|
||||
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
|
||||
if ((false == m_framestarted) || (Frame != m_frame))
|
||||
{
|
||||
m_framestarted = true;
|
||||
m_frame = Frame;
|
||||
auto const seconds{Timer::GetDeltaTime()};
|
||||
if (seconds > 0.0)
|
||||
{
|
||||
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
|
||||
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
|
||||
}
|
||||
m_time = std::fmod(m_time + seconds, 3600.0);
|
||||
if (m_camerastarted)
|
||||
{
|
||||
auto const velocity{(View.camera - m_lastcamera) / seconds};
|
||||
if (glm::length(velocity) < CAMERA_JUMP)
|
||||
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
|
||||
}
|
||||
}
|
||||
m_lastcamera = View.camera;
|
||||
m_camerastarted = true;
|
||||
}
|
||||
|
||||
// the particles, more of them in heavier rain or snow
|
||||
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
|
||||
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
|
||||
m_precipitationcount = count;
|
||||
m_precipitationsnow = Snow;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (m_precipitationcapacity < count)
|
||||
{
|
||||
m_precipitationcapacity = std::max(count, most);
|
||||
auto const size{static_cast<GLsizeiptr>(m_precipitationcapacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
|
||||
if (!m_precipitationoutput)
|
||||
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
else
|
||||
m_precipitationoutput->resize(size);
|
||||
}
|
||||
void opengl46_gpu_particles::gpu_precipitation_storage(std::uint32_t const Capacity)
|
||||
{
|
||||
auto const size{static_cast<GLsizeiptr>(Capacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
|
||||
if (!m_precipitationoutput)
|
||||
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
|
||||
else
|
||||
m_precipitationoutput->resize(size);
|
||||
}
|
||||
|
||||
void opengl46_gpu_particles::gpu_render_precipitation(precipitation_draw const &Draw)
|
||||
{
|
||||
m_precipitationtimer.begin();
|
||||
GLuint const command[4]{4, 0, 0, 0};
|
||||
m_precipitationcommand->update(command, 0, sizeof(command));
|
||||
m_precipitationoutput->bind_base();
|
||||
m_precipitationcommand->bind_base();
|
||||
auto &program{*m_precipitationprepare};
|
||||
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
|
||||
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
|
||||
::glProgramUniform4ui(program, 0, second, third, count, Snow ? 1u : 0u);
|
||||
std::array<glm::vec4, 3> offsets;
|
||||
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
|
||||
{
|
||||
auto const size{static_cast<double>(BOX_SIZE[box])};
|
||||
offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
|
||||
}
|
||||
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(offsets[0]));
|
||||
::glProgramUniform4f(program, 4, BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f);
|
||||
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
|
||||
::glProgramUniform4f(program, 5, velocity.x, velocity.y, velocity.z, kind.fall);
|
||||
::glProgramUniform4f(program, 6, static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity));
|
||||
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(View.modelview));
|
||||
::glProgramUniform4f(program, 11, 1.f, kind.forward, kind.brightness, 0.f);
|
||||
auto const viewplanes{planes(View)};
|
||||
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(viewplanes[0]));
|
||||
program.dispatch(groups(count));
|
||||
::glProgramUniform4uiv(program, 0, 1, glm::value_ptr(Draw.counts));
|
||||
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(Draw.offsets[0]));
|
||||
::glProgramUniform4fv(program, 4, 1, glm::value_ptr(Draw.sizes));
|
||||
::glProgramUniform4fv(program, 5, 1, glm::value_ptr(Draw.velocity));
|
||||
::glProgramUniform4fv(program, 6, 1, glm::value_ptr(Draw.timing));
|
||||
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(Draw.view));
|
||||
::glProgramUniform4fv(program, 11, 1, glm::value_ptr(Draw.options));
|
||||
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(Draw.planes[0]));
|
||||
program.dispatch(groups(Draw.count));
|
||||
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
// the width of a pixel a metre in front of the camera, for the least width of a streak
|
||||
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
|
||||
::glProgramUniform4f(*m_precipitationdraw, 0, View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4f(*m_precipitationdraw, 1, pixel, 0.f, 0.f, 0.f);
|
||||
::glProgramUniform4fv(*m_precipitationdraw, 0, 1, glm::value_ptr(Draw.soft));
|
||||
::glProgramUniform4fv(*m_precipitationdraw, 1, 1, glm::value_ptr(Draw.pixel));
|
||||
draw(*m_precipitationoutput, *m_precipitationcommand, *m_precipitationdraw);
|
||||
m_precipitationtimer.end();
|
||||
|
||||
m_precipitationreadback->push(*m_precipitationcommand);
|
||||
return m_precipitationdrawn;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info() const
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return {};
|
||||
std::string text;
|
||||
if (m_places > 0)
|
||||
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
|
||||
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
|
||||
if (m_precipitationcount > 0)
|
||||
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
|
||||
+ " drawn\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string opengl46_gpu_particles::info_times()
|
||||
@@ -460,13 +228,13 @@ std::string opengl46_gpu_particles::info_times()
|
||||
if (auto const time = Timer.result())
|
||||
text += std::string(text.empty() ? " particles:" : ",") + " " + Name + " " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
|
||||
};
|
||||
if (m_places > 0)
|
||||
if (places() > 0)
|
||||
{
|
||||
add(m_simulatetimer, "smoke simulation");
|
||||
add(m_smoketimer, "smoke");
|
||||
}
|
||||
if (m_precipitationcount > 0)
|
||||
add(m_precipitationtimer, m_precipitationsnow ? "snow" : "rain");
|
||||
if (precipitation_count() > 0)
|
||||
add(m_precipitationtimer, precipitation_snow() ? "snow" : "rain");
|
||||
if (false == text.empty())
|
||||
text += "\n";
|
||||
return text;
|
||||
|
||||
@@ -9,87 +9,42 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "rendering/common/gpuparticles.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
class particle_manager;
|
||||
|
||||
// particles of the gpu for the opengl 4.6 renderer: the smoke and the rain or snow.
|
||||
//
|
||||
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
|
||||
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
|
||||
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
|
||||
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
|
||||
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
|
||||
//
|
||||
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
|
||||
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
|
||||
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
|
||||
//
|
||||
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
|
||||
// their precision far from the centre of the scenery
|
||||
class opengl46_gpu_particles
|
||||
// particles of the gpu for the opengl 4.6 renderer (gfx::gpu_particles): the storage buffers, the compute programs and the
|
||||
// indirect draws
|
||||
class opengl46_gpu_particles : public gfx::gpu_particles
|
||||
{
|
||||
public:
|
||||
using view = gfx::gpu_particles::view;
|
||||
|
||||
opengl46_gpu_particles() = default;
|
||||
~opengl46_gpu_particles();
|
||||
opengl46_gpu_particles(opengl46_gpu_particles const &) = delete;
|
||||
opengl46_gpu_particles &operator=(opengl46_gpu_particles const &) = delete;
|
||||
~opengl46_gpu_particles() override;
|
||||
|
||||
// what a colour pass draws with
|
||||
struct view
|
||||
{
|
||||
glm::mat4 modelview; // world axes around the camera to the view space of the pass
|
||||
glm::mat4 projection;
|
||||
glm::dvec3 camera;
|
||||
int height{0}; // of the target, pixels
|
||||
bool soft{false}; // the resolved depth of the scene drawn so far is bound to gl::SCENE_DEPTH_TEX
|
||||
};
|
||||
|
||||
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
|
||||
bool smoke_available();
|
||||
bool precipitation_available();
|
||||
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
|
||||
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
|
||||
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
|
||||
std::size_t render_smoke(view const &View);
|
||||
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
|
||||
// returns: particles drawn, read back
|
||||
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
|
||||
// drops what is on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
|
||||
std::string info() const;
|
||||
std::string info_times();
|
||||
|
||||
private:
|
||||
struct pool_source
|
||||
{
|
||||
std::uint32_t first{0};
|
||||
std::uint32_t size{0};
|
||||
std::uint32_t head{0};
|
||||
};
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) override;
|
||||
void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) override;
|
||||
void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) override;
|
||||
void gpu_collect_precipitation(std::size_t &Drawn) override;
|
||||
void gpu_precipitation_storage(std::uint32_t const Capacity) override;
|
||||
void gpu_render_precipitation(precipitation_draw const &Draw) override;
|
||||
void gpu_clear() override;
|
||||
|
||||
bool init();
|
||||
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
|
||||
// the particles stay; otherwise all is laid out anew
|
||||
void layout(particle_manager &Particles);
|
||||
private:
|
||||
// reads back the instances counted by a pass; Drawn is left as it was when nothing came back yet
|
||||
static void collect(gl::readback &Readback, std::size_t &Drawn);
|
||||
// the planes of the view of the pass, for the culling of the compute passes
|
||||
static std::array<glm::vec4, 5> planes(view const &View);
|
||||
void draw(GLuint Output, GLuint Command, gl::program &Program);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
std::unique_ptr<gl::program> m_smokesimulate;
|
||||
std::unique_ptr<gl::program> m_smokeprepare;
|
||||
std::unique_ptr<gl::program> m_smokedraw;
|
||||
@@ -98,22 +53,12 @@ class opengl46_gpu_particles
|
||||
GLuint m_vao{0}; // the attributes of the instances, from the buffer of the draw bound to it before the draw
|
||||
|
||||
// smoke
|
||||
std::vector<pool_source> m_pool;
|
||||
std::vector<std::size_t> m_requested; // the sizes the sources asked for
|
||||
std::vector<gl::particle_smoke_source> m_sourcerecords;
|
||||
std::uint32_t m_places{0};
|
||||
bool m_poolfull{false}; // some sources got no places
|
||||
std::unique_ptr<gl::ssbo> m_particles;
|
||||
std::unique_ptr<gl::ssbo> m_sources;
|
||||
std::unique_ptr<gl::ssbo> m_owners;
|
||||
std::unique_ptr<gl::ssbo> m_smokeoutput;
|
||||
std::unique_ptr<gl::ssbo> m_smokecommand;
|
||||
std::unique_ptr<gl::readback> m_smokereadback;
|
||||
std::uint64_t m_lastupdate{0};
|
||||
glm::dvec3 m_origin{0.0};
|
||||
bool m_originset{false};
|
||||
std::size_t m_smokedrawn{0};
|
||||
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
|
||||
gl::gpu_timer m_simulatetimer;
|
||||
gl::gpu_timer m_smoketimer;
|
||||
|
||||
@@ -121,17 +66,5 @@ class opengl46_gpu_particles
|
||||
std::unique_ptr<gl::ssbo> m_precipitationoutput;
|
||||
std::unique_ptr<gl::ssbo> m_precipitationcommand;
|
||||
std::unique_ptr<gl::readback> m_precipitationreadback;
|
||||
std::uint32_t m_precipitationcapacity{0};
|
||||
unsigned int m_frame{0};
|
||||
bool m_framestarted{false};
|
||||
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
|
||||
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
|
||||
double m_time{0.0}; // seconds, for the flutter of the flakes
|
||||
glm::dvec3 m_lastcamera{0.0};
|
||||
glm::dvec3 m_cameravelocity{0.0};
|
||||
bool m_camerastarted{false};
|
||||
std::size_t m_precipitationdrawn{0};
|
||||
std::uint32_t m_precipitationcount{0};
|
||||
bool m_precipitationsnow{false};
|
||||
gl::gpu_timer m_precipitationtimer;
|
||||
};
|
||||
|
||||
@@ -12,51 +12,12 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "rendering/openglcamera.h"
|
||||
#include "model/Texture.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr max_blades{65536}; // drawn in a tile at most
|
||||
float constexpr blade_reach{1.5f}; // metres the bounds of a tile reach above its ground, for the blades
|
||||
|
||||
// the share of the blades drawn at specified distance: fewer further away, for speed (from Thinning), and fewer and fewer from
|
||||
// Fade to Reach, where the grass ends; the same as lod_share() of grass.vert
|
||||
float lod_share(float const Distance, float const Thinning, float const Fade, float const Reach)
|
||||
{
|
||||
auto const share{Distance <= Thinning ? 1.f : std::pow(Thinning / Distance, 1.5f)};
|
||||
auto const t{std::clamp((Distance - Fade) / std::max(Reach - Fade, 1e-3f), 0.f, 1.f)};
|
||||
return share * (1.f - t * t * (3.f - 2.f * t));
|
||||
}
|
||||
|
||||
// the grass through the year, by the day of the year: x how dry it is, y how fresh (the green of spring), z how grown (share
|
||||
// of the height). the turns follow the seasons of the textures (world_environment::compute_season(): winter till day 65,
|
||||
// spring till 158, summer till 252, autumn till 341): dormant and dry in winter, greening up and growing through spring,
|
||||
// fresh in May, drying out from August, the dry grass lying lower towards winter
|
||||
glm::vec3 grass_season(int const Yearday)
|
||||
{
|
||||
struct point
|
||||
{
|
||||
float day;
|
||||
glm::vec3 value;
|
||||
};
|
||||
static point const year[]{{1.f, {0.80f, 0.f, 0.55f}}, {65.f, {0.80f, 0.f, 0.55f}}, {100.f, {0.45f, 0.3f, 0.70f}}, {135.f, {0.05f, 1.f, 0.90f}},
|
||||
{170.f, {0.f, 0.4f, 1.f}}, {200.f, {0.10f, 0.f, 1.f}}, {240.f, {0.35f, 0.f, 1.f}}, {280.f, {0.50f, 0.f, 0.95f}},
|
||||
{320.f, {0.65f, 0.f, 0.80f}}, {341.f, {0.80f, 0.f, 0.60f}}, {367.f, {0.80f, 0.f, 0.55f}}};
|
||||
auto const day{std::clamp(static_cast<float>(Yearday), 1.f, 366.f)};
|
||||
for (std::size_t i = 1; i < std::size(year); ++i)
|
||||
if (day <= year[i].day)
|
||||
{
|
||||
auto const t{(day - year[i - 1].day) / (year[i].day - year[i - 1].day)};
|
||||
return glm::mix(year[i - 1].value, year[i].value, t * t * (3.f - 2.f * t));
|
||||
}
|
||||
return year[std::size(year) - 1].value;
|
||||
}
|
||||
|
||||
GLuint make_texture(GLenum const Format, int const Width, int const Height, GLenum const Filter)
|
||||
{
|
||||
GLuint texture{0};
|
||||
@@ -112,37 +73,28 @@ bool opengl46_grass::init()
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_grass::release(chunk &Chunk)
|
||||
void opengl46_grass::gpu_release(std::uint64_t const Id)
|
||||
{
|
||||
for (auto *texture : {&Chunk.heights, &Chunk.density, &Chunk.holes})
|
||||
auto const lookup{m_textures.find(Id)};
|
||||
if (lookup == m_textures.end())
|
||||
return;
|
||||
auto &chunk{lookup->second};
|
||||
for (auto *texture : {&chunk.heights, &chunk.density, &chunk.holes})
|
||||
if (*texture != 0)
|
||||
{
|
||||
::glDeleteTextures(1, texture);
|
||||
*texture = 0;
|
||||
}
|
||||
m_textures.erase(lookup);
|
||||
}
|
||||
|
||||
void opengl46_grass::clear()
|
||||
void opengl46_grass::gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source)
|
||||
{
|
||||
for (auto &entry : m_chunks)
|
||||
release(entry.second);
|
||||
m_chunks.clear();
|
||||
}
|
||||
|
||||
void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
|
||||
{
|
||||
release(Chunk);
|
||||
Chunk.version = Source.version;
|
||||
Chunk.corner = Source.corner;
|
||||
Chunk.size = Source.size;
|
||||
Chunk.cells = Source.cells;
|
||||
Chunk.samples = Source.samples;
|
||||
Chunk.kinds = Source.kinds;
|
||||
|
||||
auto &chunk{m_textures[Id]};
|
||||
auto const side{Source.cells + 1};
|
||||
Chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
|
||||
chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTextureSubImage2D(Chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
|
||||
::glTextureSubImage2D(chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
|
||||
|
||||
// the densities of the kinds side by side, as the channels of a texel
|
||||
auto const samples{Source.samples + 1};
|
||||
@@ -154,186 +106,57 @@ void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
for (std::size_t i = 0; i < plane; ++i)
|
||||
texels[i * 4 + kind] = Source.density[kind * plane + i];
|
||||
Chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
|
||||
::glTextureSubImage2D(Chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
|
||||
::glTextureSubImage2D(chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
|
||||
std::uint8_t const none{0};
|
||||
auto const holeside{(Source.holes > 0 && Source.hole_mask != nullptr) ? Source.holes + 1 : 1};
|
||||
Chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
|
||||
chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTextureSubImage2D(Chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
|
||||
::glTextureSubImage2D(chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
|
||||
// the ground and the densest place of every kind of each tile, to skip the empty tiles and to draw as many blades as needed
|
||||
auto const spacing{static_cast<double>(Source.size) / Source.cells};
|
||||
auto const step{static_cast<double>(Source.size) / Source.samples};
|
||||
auto const tilesize{static_cast<double>(Source.size) / tiles};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto &part{Chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const range = [](double const From, double const To, double const Step, int const Last) {
|
||||
return std::make_pair(std::clamp(static_cast<int>(std::floor(From / Step)), 0, Last), std::clamp(static_cast<int>(std::ceil(To / Step)), 0, Last));
|
||||
};
|
||||
auto const [gx0, gx1]{range(tx * tilesize, (tx + 1) * tilesize, spacing, Source.cells)};
|
||||
auto const [gz0, gz1]{range(tz * tilesize, (tz + 1) * tilesize, spacing, Source.cells)};
|
||||
part.low = std::numeric_limits<float>::max();
|
||||
part.high = -std::numeric_limits<float>::max();
|
||||
for (int z = gz0; z <= gz1; ++z)
|
||||
for (int x = gx0; x <= gx1; ++x)
|
||||
{
|
||||
auto const height{Source.heights[static_cast<std::size_t>(z) * side + x]};
|
||||
part.low = std::min(part.low, height);
|
||||
part.high = std::max(part.high, height);
|
||||
}
|
||||
part.most.fill(0.f);
|
||||
auto const [sx0, sx1]{range(tx * tilesize, (tx + 1) * tilesize, step, Source.samples)};
|
||||
auto const [sz0, sz1]{range(tz * tilesize, (tz + 1) * tilesize, step, Source.samples)};
|
||||
for (std::size_t kind = 0; kind < kinds; ++kind)
|
||||
{
|
||||
std::uint8_t most{0};
|
||||
for (int z = sz0; z <= sz1; ++z)
|
||||
for (int x = sx0; x <= sx1; ++x)
|
||||
most = std::max(most, Source.density[kind * plane + static_cast<std::size_t>(z) * samples + x]);
|
||||
part.most[kind] = most / 255.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_grass::update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks)
|
||||
{
|
||||
m_kinds = Kinds;
|
||||
for (auto &entry : m_chunks)
|
||||
entry.second.listed = false;
|
||||
if (false == Global.gfx_grass)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
for (auto const &source : Chunks)
|
||||
{
|
||||
if (source.heights == nullptr || source.density == nullptr || source.cells <= 0 || source.samples <= 0)
|
||||
continue;
|
||||
auto &entry{m_chunks[source.id]};
|
||||
entry.listed = true;
|
||||
if (entry.heights == 0 || entry.version != source.version)
|
||||
upload(entry, source);
|
||||
}
|
||||
// what's no longer near goes
|
||||
for (auto it = m_chunks.begin(); it != m_chunks.end();)
|
||||
{
|
||||
if (it->second.listed)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
release(it->second);
|
||||
it = m_chunks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t opengl46_grass::render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview)
|
||||
{
|
||||
if (false == Global.gfx_grass || m_chunks.empty() || m_kinds.empty() || false == init())
|
||||
if (false == prepare(Camera.position(), [&](scene::bounding_area const &Area) { return Camera.visible(Area); }) || false == init())
|
||||
return 0;
|
||||
|
||||
// whole up to the distance, thinning out to the fading distance
|
||||
auto const fade{std::max(5.f, Global.gfx_grass_distance)};
|
||||
auto const reach{std::max(fade + 1.f, Global.gfx_grass_fadedistance)};
|
||||
auto const thinning{std::max(3.f, fade * 0.2f)};
|
||||
auto const multiplier{std::max(0.f, Global.gfx_grass_density)};
|
||||
auto const camera{Camera.position()};
|
||||
|
||||
m_program->bind();
|
||||
m_vao->bind();
|
||||
::glDisable(GL_CULL_FACE);
|
||||
::glUniform4f(m_uniforms.lod, thinning, fade, reach, 0.f);
|
||||
auto const season{grass_season(simulation::Time.year_day())};
|
||||
::glUniform4f(m_uniforms.season, season.x, season.y, season.z, 0.f);
|
||||
// the wind sways the blades along it, harder in the gusts
|
||||
auto const wind{glm::vec3{simulation::Environment.wind()}};
|
||||
auto const windspeed{glm::length(glm::vec2{wind.x, wind.z})};
|
||||
auto const winddirection{windspeed > 0.01f ? glm::vec2{wind.x, wind.z} / windspeed : glm::vec2{1.f, 0.f}};
|
||||
::glUniform4f(m_uniforms.wind, winddirection.x, winddirection.y, std::clamp(windspeed / 15.f, 0.f, 1.f), static_cast<float>(std::fmod(Timer::GetTime(), 3600.0)));
|
||||
auto const &frame{this->frame()};
|
||||
::glUniform4fv(m_uniforms.lod, 1, glm::value_ptr(frame.lod));
|
||||
::glUniform4fv(m_uniforms.season, 1, glm::value_ptr(frame.season));
|
||||
::glUniform4fv(m_uniforms.wind, 1, glm::value_ptr(frame.wind));
|
||||
|
||||
std::size_t triangles{0};
|
||||
for (auto &entry : m_chunks)
|
||||
auto const &tiles{tile_draws()};
|
||||
for (auto const &chunk : chunk_draws())
|
||||
{
|
||||
auto &chunk{entry.second};
|
||||
// the kinds of the chunk, and the share of the blades of each against its densest
|
||||
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
|
||||
glm::vec4 share{0.f};
|
||||
float densest{0.f};
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
if (chunk.kinds[i] >= 0 && chunk.kinds[i] < static_cast<int>(m_kinds.size()))
|
||||
densest = std::max(densest, m_kinds[chunk.kinds[i]].density);
|
||||
if (densest <= 0.f)
|
||||
continue;
|
||||
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
|
||||
// the chunk is drawn relative to its corner, the heights as they are
|
||||
Modelview(glm::translate(View, glm::vec3{chunk.offset}));
|
||||
::glUniform4fv(m_uniforms.chunk, 1, glm::value_ptr(chunk.chunk));
|
||||
::glUniform4fv(m_uniforms.camera, 1, glm::value_ptr(chunk.camera));
|
||||
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(chunk.base[0]));
|
||||
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(chunk.tip[0]));
|
||||
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(chunk.shape[0]));
|
||||
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(chunk.share));
|
||||
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(chunk.dry[0]));
|
||||
auto const &textures{m_textures[chunk.id]};
|
||||
GLuint const units[]{textures.heights, textures.density, textures.holes};
|
||||
::glBindTextures(0, 3, units);
|
||||
for (auto i = chunk.first; i < chunk.first + chunk.count; ++i)
|
||||
{
|
||||
if (chunk.kinds[i] < 0 || chunk.kinds[i] >= static_cast<int>(m_kinds.size()))
|
||||
continue;
|
||||
auto const &kind{m_kinds[chunk.kinds[i]]};
|
||||
base[i] = glm::vec4{kind.base, 0.f};
|
||||
tip[i] = glm::vec4{kind.tip, 0.f};
|
||||
shape[i] = glm::vec4{kind.height, kind.variation, kind.width, kind.bend};
|
||||
dry[i] = glm::vec4{kind.dry, kind.seasonal};
|
||||
share[i] = kind.density / densest;
|
||||
auto const &tile{tiles[i]};
|
||||
::glUniform4fv(m_uniforms.tile, 1, glm::value_ptr(tile.tile));
|
||||
::glUniform4fv(m_uniforms.part, 1, glm::value_ptr(tile.part));
|
||||
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, tile.segments * 2 + 1, tile.count);
|
||||
}
|
||||
auto const tallest{std::max({shape[0].x, shape[1].x, shape[2].x, shape[3].x}) * 1.5f + blade_reach};
|
||||
|
||||
auto const tilesize{chunk.size / tiles};
|
||||
bool set{false};
|
||||
for (int tz = 0; tz < tiles; ++tz)
|
||||
for (int tx = 0; tx < tiles; ++tx)
|
||||
{
|
||||
auto const &part{chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
|
||||
auto const fill{std::min(1.f, glm::dot(glm::vec4{part.most[0], part.most[1], part.most[2], part.most[3]}, share))};
|
||||
if (fill <= 0.f)
|
||||
continue;
|
||||
// distance to the box of the tile
|
||||
glm::dvec3 const low{chunk.corner.x + tx * tilesize, part.low, chunk.corner.y + tz * tilesize};
|
||||
glm::dvec3 const high{low.x + tilesize, part.high + tallest, low.z + tilesize};
|
||||
auto const nearest{glm::clamp(camera, low, high)};
|
||||
auto const distance{static_cast<float>(glm::length(nearest - camera))};
|
||||
if (distance > reach)
|
||||
continue;
|
||||
if (false == Camera.visible(scene::bounding_area{(low + high) * 0.5, static_cast<float>(glm::length(high - low) * 0.5)}))
|
||||
continue;
|
||||
auto const full{tilesize * tilesize * densest * multiplier * fill};
|
||||
auto const count{std::min(max_blades, static_cast<int>(std::ceil(full * lod_share(distance, thinning, fade, reach))))};
|
||||
if (count <= 0)
|
||||
continue;
|
||||
if (false == set)
|
||||
{
|
||||
// the chunk is drawn relative to its corner, the heights as they are
|
||||
set = true;
|
||||
Modelview(glm::translate(View, glm::vec3{glm::dvec3{chunk.corner.x - camera.x, -camera.y, chunk.corner.y - camera.z}}));
|
||||
::glUniform4f(m_uniforms.chunk, chunk.size, static_cast<float>(chunk.cells), static_cast<float>(chunk.samples), 0.f);
|
||||
::glUniform4f(m_uniforms.camera, static_cast<float>(camera.x - chunk.corner.x), static_cast<float>(camera.y), static_cast<float>(camera.z - chunk.corner.y), 0.f);
|
||||
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(base[0]));
|
||||
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(tip[0]));
|
||||
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(shape[0]));
|
||||
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(share));
|
||||
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(dry[0]));
|
||||
GLuint const textures[]{chunk.heights, chunk.density, chunk.holes};
|
||||
::glBindTextures(0, 3, textures);
|
||||
}
|
||||
// fewer segments further away
|
||||
auto const segments{distance < thinning * 1.5f ? 3 : distance < fade * 0.5f ? 2 : 1};
|
||||
// the number of the tile in the world, the seed of its blades
|
||||
auto const seedx{static_cast<float>(std::floor(chunk.corner.x / tilesize + 0.5) + tx)};
|
||||
auto const seedz{static_cast<float>(std::floor(chunk.corner.y / tilesize + 0.5) + tz)};
|
||||
::glUniform4f(m_uniforms.tile, tx * tilesize, tz * tilesize, tilesize, std::max(1.f, full));
|
||||
::glUniform4f(m_uniforms.part, fill, static_cast<float>(segments), seedx, seedz);
|
||||
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, segments * 2 + 1, count);
|
||||
triangles += static_cast<std::size_t>(count) * (segments * 2 - 1);
|
||||
}
|
||||
}
|
||||
|
||||
::glEnable(GL_CULL_FACE);
|
||||
gl::vao::unbind();
|
||||
// the units of the textures were taken behind the back of the texture cache
|
||||
opengl_texture::reset_unit_cache();
|
||||
return triangles;
|
||||
return triangles();
|
||||
}
|
||||
|
||||
@@ -13,69 +13,44 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/common/grassfield.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/vao.h"
|
||||
|
||||
class opengl_camera;
|
||||
|
||||
// grass of the heightmap terrain for the opengl 4.6 renderer: blades made up by the vertex shader (grass.vert) where the
|
||||
// grass is painted, in the chunks near the camera.
|
||||
//
|
||||
// a chunk is cut into tiles; each tile visible within the reach of the grass is drawn as instances of a blade, as many as
|
||||
// its densest kind and its distance ask for. a blade finds its place, its kind and its shape from its number, so the same
|
||||
// blades stand at the same places in every frame. further away the blades thin out and grow wider, and fade out at the end
|
||||
// of the reach. the heights, the densities and the holes of a chunk are kept in textures, made when the chunk comes near
|
||||
class opengl46_grass
|
||||
// grass of the heightmap terrain for the opengl 4.6 renderer (gfx::grass_field): the textures of the chunks, and the draws of
|
||||
// the tiles seen, instances of a blade made up by grass.vert
|
||||
class opengl46_grass : public gfx::grass_field
|
||||
{
|
||||
public:
|
||||
opengl46_grass() = default;
|
||||
~opengl46_grass();
|
||||
~opengl46_grass() override;
|
||||
|
||||
// see gfx_renderer::Terrain_Grass()
|
||||
void update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks);
|
||||
// draws the grass seen by the camera. View: the modelview of the pass with the camera at its origin; Modelview: hands a
|
||||
// modelview to the model block of the draws which follow. returns: triangles drawn
|
||||
std::size_t render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview);
|
||||
// drops everything made on the gpu, e.g. when the context goes
|
||||
void clear();
|
||||
bool empty() const { return m_chunks.empty(); }
|
||||
|
||||
static int constexpr tiles{16}; // along a side of a chunk
|
||||
protected:
|
||||
void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) override;
|
||||
void gpu_release(std::uint64_t const Id) override;
|
||||
|
||||
private:
|
||||
struct tile
|
||||
struct chunk_textures
|
||||
{
|
||||
float low{0.f}, high{0.f}; // of the ground
|
||||
std::array<float, 4> most{}; // the highest density of each kind of the chunk within the tile, 0..1
|
||||
};
|
||||
struct chunk
|
||||
{
|
||||
std::uint64_t version{0};
|
||||
glm::dvec2 corner{0.0};
|
||||
float size{0.f};
|
||||
int cells{0};
|
||||
int samples{0};
|
||||
std::array<int, 4> kinds{-1, -1, -1, -1};
|
||||
GLuint heights{0}; // R32F, the grid points
|
||||
GLuint density{0}; // RGBA8, the densities of the kinds
|
||||
GLuint holes{0}; // R8, a single texel of 0 without holes
|
||||
std::array<tile, tiles * tiles> parts;
|
||||
bool listed{false}; // in the last list given
|
||||
};
|
||||
|
||||
bool init();
|
||||
void upload(chunk &Chunk, gfx::grass_chunk const &Source);
|
||||
void release(chunk &Chunk);
|
||||
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
std::unique_ptr<gl::vao> m_vao; // without attributes, the blades are made of the numbers of their vertices
|
||||
std::vector<gfx::grass_kind> m_kinds;
|
||||
std::unordered_map<std::uint64_t, chunk> m_chunks;
|
||||
std::unordered_map<std::uint64_t, chunk_textures> m_textures;
|
||||
// locations of the uniforms of the program
|
||||
struct locations
|
||||
{
|
||||
|
||||
@@ -10,10 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46instancecull.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "model/Texture.h"
|
||||
#include "rendering/frustum.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
@@ -36,6 +33,7 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
|
||||
if (Shared != nullptr)
|
||||
{
|
||||
m_shared = Shared->m_shared;
|
||||
share_records(*Shared);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -59,203 +57,11 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
|
||||
m_output = std::make_unique<gl::ssbo>(65536 * sizeof(glm::mat4), gl::INSTANCE_OUTPUT_SSBO_BINDING);
|
||||
m_commands = std::make_unique<gl::ssbo>(4096 * sizeof(gl::instance_draw_command), gl::INSTANCE_COMMAND_SSBO_BINDING);
|
||||
m_readback = std::make_unique<gl::readback>(readbacksize);
|
||||
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
|
||||
{
|
||||
collect();
|
||||
|
||||
m_inpass = true;
|
||||
m_ran = true;
|
||||
m_dispatched = false;
|
||||
m_setup = Setup;
|
||||
m_buckets.clear();
|
||||
m_joblist.clear();
|
||||
m_grouplist.clear();
|
||||
m_drawgroups.clear();
|
||||
m_lodlist.clear();
|
||||
m_groupmap.clear();
|
||||
m_sleepergroupmap.clear();
|
||||
m_cpucounts.clear();
|
||||
m_candidates = 0;
|
||||
m_entries = 0;
|
||||
m_sleepertracks = 0;
|
||||
m_sleepers = 0;
|
||||
m_models = 0;
|
||||
m_counts = {};
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, false);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
if (!m_inpass)
|
||||
return;
|
||||
++m_counts.skippedbuckets;
|
||||
m_counts.skippedinstances += Bucket.records.size();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
|
||||
{
|
||||
add(Key, Bucket, Area, true);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
|
||||
{
|
||||
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
|
||||
return;
|
||||
|
||||
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
|
||||
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
|
||||
// camera of the pass
|
||||
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
|
||||
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
|
||||
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
|
||||
auto const centerdistance{glm::length(Area.center - distancecamera)};
|
||||
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
|
||||
auto const farthest{centerdistance + Area.radius};
|
||||
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
|
||||
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
|
||||
|
||||
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
{
|
||||
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
|
||||
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
|
||||
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
|
||||
|
||||
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
||||
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
||||
m_entries += group.entrycount;
|
||||
lookup = groupmap.emplace(Key, group).first;
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
// any instance of the group serves; it's only prepared and asked for its material
|
||||
TAnimModel *instance{nullptr};
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lookup->second.distancemin = std::min(lookup->second.distancemin, distancemin);
|
||||
lookup->second.distancemax = std::max(lookup->second.distancemax, distancemax);
|
||||
if (m_setup.drawing)
|
||||
{
|
||||
auto &drawgroup{m_drawgroups[lookup->second.index]};
|
||||
drawgroup.distancemin = lookup->second.distancemin;
|
||||
drawgroup.distancemax = lookup->second.distancemax;
|
||||
if (drawgroup.instance == nullptr)
|
||||
{
|
||||
for (auto *candidate : Bucket.instances)
|
||||
{
|
||||
if (candidate != nullptr)
|
||||
{
|
||||
drawgroup.instance = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
place(Bucket);
|
||||
|
||||
gl::instance_cull_job job{};
|
||||
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
|
||||
job.first = Bucket.gpufirst;
|
||||
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
|
||||
job.count = static_cast<std::uint32_t>(Bucket.records.size());
|
||||
job.group = lookup->second.index;
|
||||
job.candidatestart = m_candidates;
|
||||
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
|
||||
m_joblist.push_back(job);
|
||||
m_buckets.push_back(&Bucket);
|
||||
m_candidates += job.count;
|
||||
if (Sleepers)
|
||||
{
|
||||
++m_sleepertracks;
|
||||
m_sleepers += job.count;
|
||||
}
|
||||
else if (Key.instance != nullptr)
|
||||
{
|
||||
++m_models;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
|
||||
{
|
||||
auto &shared{*m_shared};
|
||||
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
|
||||
auto const current{Bucket.gpugeneration == shared.recordgeneration};
|
||||
if (current && !Bucket.gpustale)
|
||||
return;
|
||||
if (current && count <= Bucket.gpucapacity)
|
||||
{
|
||||
shared.records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
|
||||
m_counts.sentrecords += count;
|
||||
Bucket.gpustale = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// a new place, with some room for instances the editor adds
|
||||
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
|
||||
auto const records{static_cast<std::uint32_t>(Target.records.size())};
|
||||
Target.gpufirst = shared.recordfill;
|
||||
Target.gpucapacity = records + records / 4;
|
||||
Target.gpugeneration = shared.recordgeneration;
|
||||
Target.gpustale = false;
|
||||
shared.recordfill += Target.gpucapacity;
|
||||
shared.records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
|
||||
m_counts.sentrecords += records;
|
||||
};
|
||||
|
||||
auto const capacity{static_cast<std::uint32_t>(shared.records->size() / RECORD_SIZE)};
|
||||
if (shared.recordfill + count + count / 4 > capacity)
|
||||
{
|
||||
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
|
||||
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
|
||||
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
|
||||
// so a buffer started over again in the same or the next frame grows
|
||||
GLsizeiptr live{count + count / 4};
|
||||
for (auto const *bucket : m_buckets)
|
||||
live += bucket->records.size() + bucket->records.size() / 4;
|
||||
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
|
||||
if ((2 * live * RECORD_SIZE > shared.records->size()) || again)
|
||||
shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size()));
|
||||
shared.restarted = true;
|
||||
shared.restartframe = m_setup.frame;
|
||||
m_counts.restarted = true;
|
||||
++shared.recordgeneration;
|
||||
shared.recordfill = 0;
|
||||
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||
{
|
||||
allocate(*m_buckets[job]);
|
||||
m_joblist[job].first = m_buckets[job]->gpufirst;
|
||||
}
|
||||
}
|
||||
allocate(Bucket);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion, bool const Twophase)
|
||||
{
|
||||
m_occlusion = false;
|
||||
m_twophase = false;
|
||||
if (!m_inpass || m_joblist.empty())
|
||||
if (!dispatch_begin())
|
||||
return;
|
||||
|
||||
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t))};
|
||||
@@ -277,14 +83,8 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
bind_buffers();
|
||||
|
||||
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
||||
auto const &frustumplanes{m_setup.frustum->planes()};
|
||||
glm::vec4 planes[6];
|
||||
for (int plane = 0; plane < 6; ++plane)
|
||||
{
|
||||
auto const *source{frustumplanes[plane]};
|
||||
planes[plane] = glm::vec4{source[0], source[1], source[2],
|
||||
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
|
||||
}
|
||||
frustum_planes(planes);
|
||||
glProgramUniformMatrix4fv(*m_shared->cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
|
||||
glProgramUniform4fv(*m_shared->cull_program, 1, 6, glm::value_ptr(planes[0]));
|
||||
glProgramUniform4f(*m_shared->cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
|
||||
@@ -328,8 +128,7 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
m_cpucounts.assign(m_entries, 0);
|
||||
m_dispatched = true;
|
||||
dispatch_end();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::bind_buffers()
|
||||
@@ -401,144 +200,29 @@ void opengl46_instance_cull::bind_draw()
|
||||
glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &output);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
|
||||
std::int64_t opengl46_instance_cull::record_buffer_size() const
|
||||
{
|
||||
m_draws = Draws;
|
||||
m_prepassdraws = Prepassdraws;
|
||||
m_calls = Calls;
|
||||
m_prepasscalls = Prepasscalls;
|
||||
m_cputime = Cputime;
|
||||
return m_shared->records->size();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
|
||||
void opengl46_instance_cull::resize_record_buffer(std::int64_t const Size)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
|
||||
auto const lookup{groupmap.find(Key)};
|
||||
if (lookup == groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
|
||||
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
|
||||
m_shared->records->resize(static_cast<GLsizeiptr>(Size));
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
|
||||
void opengl46_instance_cull::send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count)
|
||||
{
|
||||
if (!m_dispatched)
|
||||
return;
|
||||
auto const lookup{m_groupmap.find(Key)};
|
||||
if (lookup == m_groupmap.end())
|
||||
return;
|
||||
auto const &group{lookup->second};
|
||||
auto const &lods{m_grouplist[group.index]};
|
||||
// the band the culling gives the distance: the number of bounds it reached
|
||||
auto const first{m_lodlist.begin() + lods.lodfirst};
|
||||
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
|
||||
if (band < group.entrycount)
|
||||
++m_cpucounts[group.entryfirst + band];
|
||||
m_shared->records->update(Records, static_cast<GLintptr>(First) * RECORD_SIZE, static_cast<GLsizeiptr>(Count) * RECORD_SIZE);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::end_pass()
|
||||
void opengl46_instance_cull::push_counts()
|
||||
{
|
||||
// the counts of both phases, for the panel and the comparison
|
||||
if (m_dispatched)
|
||||
m_readback->push(*m_counters);
|
||||
// the cpu counts are there to compare with only when the cpu drew
|
||||
if (m_dispatched && !m_setup.drawing)
|
||||
{
|
||||
std::uint64_t total{0};
|
||||
for (auto const count : m_cpucounts)
|
||||
total += count;
|
||||
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
|
||||
// the gpu is never this far behind
|
||||
while (m_cpupasses.size() > 8)
|
||||
m_cpupasses.pop_front();
|
||||
}
|
||||
m_lastsleepertracks = m_sleepertracks;
|
||||
m_lastsleepers = m_sleepers;
|
||||
m_lastmodels = m_models;
|
||||
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
|
||||
m_counts.candidates = m_candidates;
|
||||
m_lastcounts = m_counts;
|
||||
m_inpass = false;
|
||||
m_dispatched = false;
|
||||
m_readback->push(*m_counters);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::collect()
|
||||
bool opengl46_instance_cull::pop_counts(std::uint32_t *Counts)
|
||||
{
|
||||
if (!m_readback || !m_readback->pop(m_readbackdata.data()))
|
||||
return;
|
||||
|
||||
auto const frame{m_readbackdata[0]};
|
||||
comparison result;
|
||||
result.frame = frame;
|
||||
result.occluded = m_readbackdata[3];
|
||||
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
|
||||
result.entries = m_readbackdata[2];
|
||||
result.first = m_readbackdata[4];
|
||||
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||
m_cpupasses.pop_front();
|
||||
if (m_cpupasses.empty())
|
||||
{
|
||||
// drawn from the gpu: nothing to compare with
|
||||
m_last = result;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const &cpu{m_cpupasses.front()};
|
||||
result.cputotal = cpu.total;
|
||||
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
|
||||
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
|
||||
{
|
||||
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
|
||||
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
|
||||
if (difference > 0)
|
||||
{
|
||||
++result.mismatched;
|
||||
result.largestdifference = std::max(result.largestdifference, difference);
|
||||
}
|
||||
}
|
||||
m_cpupasses.pop_front();
|
||||
|
||||
m_last = result;
|
||||
++m_comparedframes;
|
||||
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
|
||||
++m_mismatchedframes;
|
||||
}
|
||||
|
||||
std::string opengl46_instance_cull::info(std::string const &Label, bool const Occlusion)
|
||||
{
|
||||
if (Global.gfx_gpucull == 0)
|
||||
return {};
|
||||
// the sleepers are among the instances counted
|
||||
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
|
||||
// and so are the models drawn on their own
|
||||
if (m_lastmodels > 0)
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
|
||||
// what the pass tested and sent to the gpu
|
||||
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
|
||||
if (Global.gfx_gpucull_bucketrange)
|
||||
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling";
|
||||
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
if (m_last.frame == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
if (false == Occlusion)
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
|
||||
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
|
||||
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
|
||||
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
|
||||
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
|
||||
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
|
||||
}
|
||||
if (m_comparedframes == 0)
|
||||
return "\n" + Label + ": no result yet" + sleepers;
|
||||
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
|
||||
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
|
||||
std::to_string(m_comparedframes) + sleepers;
|
||||
return (m_readback && m_readback->pop(Counts));
|
||||
}
|
||||
|
||||
std::string opengl46_instance_cull::info_times(std::string const &Label)
|
||||
|
||||
@@ -21,53 +21,15 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/query.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "scene/scene.h"
|
||||
#include "rendering/common/instancecull.h"
|
||||
|
||||
class cFrustum;
|
||||
class TAnimModel;
|
||||
|
||||
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp),
|
||||
// the gpu side of opengl46_renderer::Queue_Instances() and Render_Instanced():
|
||||
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
|
||||
// in its own place, and sent again only when the bucket made them anew
|
||||
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
|
||||
// lod bands are its entries
|
||||
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
|
||||
// modelview matrices of the instances there
|
||||
// with Global.gfx_gpucull 1 the instances are drawn from the result: the renderer turns each lod band of a group into a
|
||||
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
|
||||
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer
|
||||
// (opengl46_renderer::Render_Instances_Indirect()). an occlusion test against a depth pyramid of what the pass drew before
|
||||
// (opengl46_hiz) can leave out more instances. in two phases the instances seen in the previous frame are culled and drawn
|
||||
// first, then the pyramid is made again with them in it (dispatch_second()) and the others are tested against it, so the
|
||||
// instances hide one another too. the command buffer holds the commands three times over: the instances drawn first, all
|
||||
// of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
|
||||
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
|
||||
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
|
||||
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map, the
|
||||
// shadow maps of headlights and the scenery parts of those of lamps) has a culling of its own, with its own buffers, while the
|
||||
// records and the programs are shared by all of them: the records of a bucket go to the gpu once and serve every pass which
|
||||
// queues the bucket
|
||||
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
|
||||
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
|
||||
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
|
||||
// its key), whose submodel tree the renderer animates and walks for it alone
|
||||
class opengl46_instance_cull
|
||||
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp,
|
||||
// instance_cull_commands.comp): the buffers and the passes of gfx::instance_cull, which tells what they do. with
|
||||
// Global.gfx_gpucull 1 the renderer draws the result with opengl46_renderer::Render_Instances_Indirect(); the occlusion test
|
||||
// takes the depth pyramid of opengl46_hiz
|
||||
class opengl46_instance_cull : public gfx::instance_cull
|
||||
{
|
||||
public:
|
||||
// what a pass needs for the tests, the way Render_Instanced() takes it
|
||||
struct pass_setup
|
||||
{
|
||||
unsigned int frame{0};
|
||||
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
|
||||
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
|
||||
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
|
||||
float distancescale{1.f}; // of the squared distance
|
||||
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
|
||||
float distancerange{0.f}; // squared range of the scaled distance before the offset
|
||||
float drawrange{0.f}; // squared draw range
|
||||
float zoom{1.f};
|
||||
bool drawing{false}; // the instances are drawn from the result (Global.gfx_gpucull 1), not compared
|
||||
};
|
||||
// occlusion test of a pass
|
||||
struct occlusion_setup
|
||||
{
|
||||
@@ -77,47 +39,11 @@ class opengl46_instance_cull
|
||||
glm::vec2 screen{0.f}; // size of the depth it was made from, pixels
|
||||
glm::mat4 projection{1.f}; // of the pass
|
||||
};
|
||||
// a group of instances (model and skins) of the pass, for the renderer to make the draw commands of
|
||||
struct draw_group
|
||||
{
|
||||
scene::basic_cell::instance_bucket_key const *key;
|
||||
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
|
||||
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
|
||||
std::uint32_t lodfirst; // in lod_bounds()
|
||||
std::uint32_t lodcount;
|
||||
std::uint32_t entryfirst;
|
||||
float distancemin; // squared distances (scaled as the culling does) the instances of the group can be at
|
||||
float distancemax;
|
||||
};
|
||||
|
||||
opengl46_instance_cull() = default;
|
||||
opengl46_instance_cull(const opengl46_instance_cull &) = delete;
|
||||
opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete;
|
||||
|
||||
// creates the buffers, and the programs and the record buffer, or takes the ones of Shared. throws gl::shader_exception
|
||||
void init(opengl46_instance_cull const *Shared = nullptr);
|
||||
// starts collecting the buckets of a pass
|
||||
void begin_pass(pass_setup const &Setup);
|
||||
bool in_pass() const
|
||||
{
|
||||
return m_inpass;
|
||||
}
|
||||
bool drawing() const
|
||||
{
|
||||
return m_inpass && m_setup.drawing;
|
||||
}
|
||||
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed.
|
||||
// Area: bounds which hold the locations of all its visible instances: their own (opengl46_renderer::Bucket_Area()) or of the cell
|
||||
// holding the bucket
|
||||
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place
|
||||
// in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by
|
||||
// the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the
|
||||
// camera of the pass (Render_Sleepers())
|
||||
void add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted,
|
||||
// for the panel
|
||||
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid
|
||||
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
|
||||
// frame pass for now, dispatch_second() takes the others
|
||||
@@ -126,108 +52,22 @@ class opengl46_instance_cull
|
||||
// the others are tested against it, then the commands of all instances and of the ones of this phase are filled in.
|
||||
// needs fill_commands() before it
|
||||
void dispatch_second(occlusion_setup const &Occlusion);
|
||||
// the last dispatch() works in two phases
|
||||
bool two_phase() const
|
||||
{
|
||||
return m_twophase;
|
||||
}
|
||||
// groups of the pass and the lod bounds they refer to, valid from dispatch() to end_pass()
|
||||
std::vector<draw_group> const &draw_groups() const
|
||||
{
|
||||
return m_drawgroups;
|
||||
}
|
||||
std::vector<float> const &lod_bounds() const
|
||||
{
|
||||
return m_lodlist;
|
||||
}
|
||||
// sends the indirect draw commands made for the entries of the pass and fills in their instances (of the instances drawn
|
||||
// first, in two phases). after this the commands are in the buffer bound by bind_draw(), the modelview matrices of the
|
||||
// instances in the output
|
||||
void fill_commands(std::vector<gl::instance_draw_command> const &Commands);
|
||||
// copies of the commands in the command buffer
|
||||
enum class command_set : std::uint32_t
|
||||
{
|
||||
first = 0, // instances which passed dispatch(): in one phase all of them
|
||||
all = 1, // after dispatch_second()
|
||||
second = 2 // the ones which passed dispatch_second()
|
||||
};
|
||||
// offset of command Index of a copy in the command buffer
|
||||
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
|
||||
{
|
||||
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
|
||||
}
|
||||
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
|
||||
void bind_draw();
|
||||
// counts of the last result which came back from the gpu, with the draws of the pass and the gl calls which made them (fewer
|
||||
// with multi draws), for the debug panel
|
||||
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
|
||||
// what draw_result() was given last
|
||||
std::uint32_t prepass_draws() const
|
||||
{
|
||||
return m_prepassdraws;
|
||||
}
|
||||
std::uint32_t prepass_calls() const
|
||||
{
|
||||
return m_prepasscalls;
|
||||
}
|
||||
double cpu_time() const
|
||||
{
|
||||
return m_cputime;
|
||||
}
|
||||
// instances which passed in the last result which came back from the gpu, a few frames old
|
||||
std::uint32_t last_count() const
|
||||
{
|
||||
return static_cast<std::uint32_t>(m_last.gputotal);
|
||||
}
|
||||
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
|
||||
// Render_Sleepers() drew. needs dispatch() before it
|
||||
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
|
||||
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
|
||||
// scales it: counted in the lod band the distance falls in. needs dispatch() before it
|
||||
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
|
||||
// ends the pass: its cpu counts are kept for the comparison
|
||||
void end_pass();
|
||||
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
|
||||
// for the panel also by the culls of passes which aren't made every frame
|
||||
void collect();
|
||||
// a pass was culled since the start
|
||||
bool ran() const
|
||||
{
|
||||
return m_ran;
|
||||
}
|
||||
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
|
||||
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
|
||||
std::string info_times(std::string const &Label = "instance cull");
|
||||
|
||||
private:
|
||||
struct group_data
|
||||
{
|
||||
std::uint32_t index;
|
||||
std::uint32_t entryfirst;
|
||||
std::uint32_t entrycount;
|
||||
float distancemin;
|
||||
float distancemax;
|
||||
};
|
||||
// counts of the cpu of a pass, waiting for those of the gpu
|
||||
struct cpu_pass
|
||||
{
|
||||
unsigned int frame;
|
||||
std::vector<std::uint32_t> counts;
|
||||
std::uint64_t total;
|
||||
};
|
||||
struct comparison
|
||||
{
|
||||
unsigned int frame{0};
|
||||
std::uint64_t gputotal{0};
|
||||
std::uint64_t cputotal{0};
|
||||
std::uint32_t entries{0};
|
||||
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
|
||||
std::uint32_t mismatched{0};
|
||||
std::uint32_t largestdifference{0};
|
||||
std::uint32_t occluded{0}; // left out by the occlusion test
|
||||
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
|
||||
};
|
||||
protected:
|
||||
std::int64_t record_buffer_size() const override;
|
||||
void resize_record_buffer(std::int64_t const Size) override;
|
||||
void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) override;
|
||||
void push_counts() override;
|
||||
bool pop_counts(std::uint32_t *Counts) override;
|
||||
|
||||
private:
|
||||
// what the culls of all passes share
|
||||
struct shared_data
|
||||
{
|
||||
@@ -235,26 +75,11 @@ class opengl46_instance_cull
|
||||
std::unique_ptr<gl::program> offsets_program;
|
||||
std::unique_ptr<gl::program> commands_program;
|
||||
std::unique_ptr<gl::ssbo> records;
|
||||
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
|
||||
std::uint32_t recordfill{0};
|
||||
unsigned int recordgeneration{1};
|
||||
// frame of the last time the buffer was started over, and whether it was
|
||||
unsigned int restartframe{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
|
||||
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
|
||||
|
||||
// add_bucket() and add_sleepers()
|
||||
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
|
||||
// finds a place for the records of a bucket in the record buffer and sends them
|
||||
void place(scene::basic_cell::instance_bucket const &Bucket);
|
||||
// binds the buffers of the culling passes
|
||||
void bind_buffers();
|
||||
|
||||
// entries read back for the comparison; with more of them only the totals and these are compared
|
||||
static constexpr std::uint32_t READBACK_ENTRIES{16384};
|
||||
|
||||
std::shared_ptr<shared_data> m_shared;
|
||||
std::unique_ptr<gl::ssbo> m_jobs;
|
||||
std::unique_ptr<gl::ssbo> m_groups;
|
||||
@@ -265,55 +90,4 @@ class opengl46_instance_cull
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
gl::gpu_timer m_timer;
|
||||
gl::gpu_timer m_secondtimer;
|
||||
|
||||
bool m_inpass{false};
|
||||
bool m_ran{false};
|
||||
pass_setup m_setup;
|
||||
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
|
||||
std::vector<gl::instance_cull_job> m_joblist;
|
||||
std::vector<gl::instance_cull_group> m_grouplist;
|
||||
std::vector<draw_group> m_drawgroups;
|
||||
std::vector<float> m_lodlist;
|
||||
group_map m_groupmap;
|
||||
group_map m_sleepergroupmap; // groups of the sleepers
|
||||
std::uint32_t m_candidates{0};
|
||||
std::uint32_t m_entries{0};
|
||||
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_sleepertracks{0};
|
||||
std::uint32_t m_sleepers{0};
|
||||
std::uint32_t m_lastsleepertracks{0};
|
||||
std::uint32_t m_lastsleepers{0};
|
||||
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
|
||||
std::uint32_t m_models{0};
|
||||
std::uint32_t m_lastmodels{0};
|
||||
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
|
||||
// their range and their instances, records sent to the gpu, the record buffer started over
|
||||
struct pass_counts
|
||||
{
|
||||
std::uint32_t jobs{0};
|
||||
std::uint32_t candidates{0};
|
||||
std::uint32_t skippedbuckets{0};
|
||||
std::uint64_t skippedinstances{0};
|
||||
std::uint64_t sentrecords{0};
|
||||
bool restarted{false};
|
||||
};
|
||||
pass_counts m_counts;
|
||||
pass_counts m_lastcounts;
|
||||
bool m_dispatched{false};
|
||||
|
||||
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
|
||||
std::deque<cpu_pass> m_cpupasses;
|
||||
std::vector<std::uint32_t> m_readbackdata;
|
||||
comparison m_last;
|
||||
std::uint64_t m_comparedframes{0};
|
||||
std::uint64_t m_mismatchedframes{0};
|
||||
// draws of the last pass drawn from the gpu
|
||||
std::uint32_t m_draws{0};
|
||||
std::uint32_t m_prepassdraws{0};
|
||||
std::uint32_t m_calls{0};
|
||||
std::uint32_t m_prepasscalls{0};
|
||||
std::uint32_t m_commandcount{0};
|
||||
double m_cputime{0.0};
|
||||
bool m_occlusion{false}; // the last dispatch tested occlusion
|
||||
bool m_twophase{false}; // in two phases
|
||||
};
|
||||
|
||||
@@ -19,17 +19,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
|
||||
float const SHADOW_NEAR{0.15f};
|
||||
// near plane of the maps (gfx::local_shadows)
|
||||
float const SHADOW_NEAR{gfx::local_shadows::shadow_near};
|
||||
// uniform locations of shadowcube.geom
|
||||
GLint const NEAR_LOCATION{0};
|
||||
GLint const LAYER_LOCATION{1};
|
||||
// layer of the map of the cab lamp in the array
|
||||
int const CAB_SLOT{0};
|
||||
// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres
|
||||
double const LAMP_MOVED{0.01};
|
||||
// lamps with a shorter reach aren't worth a map, metres
|
||||
float const LAMP_RANGE_MIN{1.f};
|
||||
int const CAB_SLOT{gfx::local_shadows::cab_slot};
|
||||
|
||||
// cube map array of depth maps compared to the reference, one level, Cubes cubes of Size x Size faces
|
||||
GLuint make_cube_array(int const Size, int const Cubes)
|
||||
@@ -99,9 +95,9 @@ void setup_drawing(GLuint const Framebuffer, int const Size)
|
||||
|
||||
opengl46_local_shadows::~opengl46_local_shadows()
|
||||
{
|
||||
release();
|
||||
release_lamps();
|
||||
release_headlights();
|
||||
gpu_release();
|
||||
gpu_release_lamps();
|
||||
gpu_release_headlights();
|
||||
if (m_empty != 0)
|
||||
glDeleteTextures(1, &m_empty);
|
||||
if (m_emptyheadlights != 0)
|
||||
@@ -167,40 +163,14 @@ void opengl46_local_shadows::set_layer(int const Layer)
|
||||
glProgramUniform1i(*m_alpha_multidraw_program, LAYER_LOCATION, Layer);
|
||||
}
|
||||
|
||||
opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Level)
|
||||
bool opengl46_local_shadows::gpu_allocate(int const Size)
|
||||
{
|
||||
switch (Level)
|
||||
{
|
||||
case 1:
|
||||
return {512, 4, 0.f, 1, 1024, 2, 8};
|
||||
case 2:
|
||||
return {1024, 2, 1.f, 1, 1024, 1, 4};
|
||||
case 3:
|
||||
return {1024, 1, 2.f, 2, 2048, 1, 2};
|
||||
default:
|
||||
return {0, 0, 0.f, 0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate(int const Size)
|
||||
{
|
||||
release();
|
||||
if (Size <= 0)
|
||||
return;
|
||||
m_texture = make_cube_array(Size, CAB_SLOT + 1);
|
||||
m_framebuffer = make_framebuffer(m_texture);
|
||||
if (m_framebuffer == 0)
|
||||
{
|
||||
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
|
||||
release();
|
||||
Global.gfx_lights_shadows = 0;
|
||||
return;
|
||||
}
|
||||
m_size = Size;
|
||||
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
|
||||
return (m_framebuffer != 0);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release()
|
||||
void opengl46_local_shadows::gpu_release()
|
||||
{
|
||||
if (m_framebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_framebuffer);
|
||||
@@ -208,32 +178,17 @@ void opengl46_local_shadows::release()
|
||||
glDeleteTextures(1, &m_texture);
|
||||
m_framebuffer = 0;
|
||||
m_texture = 0;
|
||||
m_size = 0;
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate_lamps(int const Count, int const Size)
|
||||
bool opengl46_local_shadows::gpu_allocate_lamps(int const Count, int const Size)
|
||||
{
|
||||
release_lamps();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
// the scenery parts and the working copies
|
||||
m_lampstexture = make_cube_array(Size, 2 * Count);
|
||||
m_lampsframebuffer = make_framebuffer(m_lampstexture);
|
||||
if (m_lampsframebuffer == 0)
|
||||
{
|
||||
ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows");
|
||||
release_lamps();
|
||||
Global.gfx_lights_shadows_lamps = 0;
|
||||
return;
|
||||
}
|
||||
m_lampssize = Size;
|
||||
m_slots.assign(static_cast<std::size_t>(Count), lamp_slot{});
|
||||
WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
|
||||
std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
return (m_lampsframebuffer != 0);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release_lamps()
|
||||
void opengl46_local_shadows::gpu_release_lamps()
|
||||
{
|
||||
if (m_lampsframebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_lampsframebuffer);
|
||||
@@ -241,35 +196,19 @@ void opengl46_local_shadows::release_lamps()
|
||||
glDeleteTextures(1, &m_lampstexture);
|
||||
m_lampsframebuffer = 0;
|
||||
m_lampstexture = 0;
|
||||
m_lampssize = 0;
|
||||
m_slots.clear();
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate_headlights(int const Count, int const Size)
|
||||
bool opengl46_local_shadows::gpu_allocate_headlights(int const Count, int const Size)
|
||||
{
|
||||
release_headlights();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
m_headlightstexture = make_array(Size, Count);
|
||||
glCreateFramebuffers(1, &m_headlightsframebuffer);
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, 0);
|
||||
glNamedFramebufferDrawBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
glNamedFramebufferReadBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
if (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
|
||||
release_headlights();
|
||||
Global.gfx_lights_shadows_headlights = 0;
|
||||
return;
|
||||
}
|
||||
m_headlightssize = Size;
|
||||
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
|
||||
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
|
||||
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
return (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release_headlights()
|
||||
void opengl46_local_shadows::gpu_release_headlights()
|
||||
{
|
||||
if (m_headlightsframebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_headlightsframebuffer);
|
||||
@@ -277,9 +216,6 @@ void opengl46_local_shadows::release_headlights()
|
||||
glDeleteTextures(1, &m_headlightstexture);
|
||||
m_headlightsframebuffer = 0;
|
||||
m_headlightstexture = 0;
|
||||
m_headlightssize = 0;
|
||||
m_headlightslots.clear();
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::bind()
|
||||
@@ -289,42 +225,8 @@ void opengl46_local_shadows::bind()
|
||||
glBindTextureUnit(gl::HEADLIGHT_SHADOW_TEX, (m_headlightstexture != 0) ? m_headlightstexture : m_emptyheadlights);
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
|
||||
void opengl46_local_shadows::gpu_begin_cab()
|
||||
{
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
if (level != m_level)
|
||||
{
|
||||
m_level = level;
|
||||
auto const size{setup(level).size};
|
||||
if (size != m_size)
|
||||
allocate(size);
|
||||
m_valid = false;
|
||||
}
|
||||
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
|
||||
{
|
||||
// with the lamp off nothing reads the map; once it is on again the cab may have changed
|
||||
m_valid = false;
|
||||
return false;
|
||||
}
|
||||
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
// the moving parts of the cab are followed every few frames
|
||||
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_vehicle = Vehicle;
|
||||
m_cab = Cab;
|
||||
m_lamp = Lamp;
|
||||
m_drawnframe = Framestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin()
|
||||
{
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
m_timer.begin();
|
||||
set_layer(CAB_SLOT * 6);
|
||||
setup_drawing(m_framebuffer, m_size);
|
||||
@@ -332,308 +234,46 @@ void opengl46_local_shadows::begin()
|
||||
glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_cab()
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
gl::framebuffer::unbind();
|
||||
m_timer.end();
|
||||
m_valid = true;
|
||||
m_drawcalls = Drawcalls;
|
||||
++m_updatecount;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::weights() const
|
||||
void opengl46_local_shadows::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles)
|
||||
{
|
||||
// a face spans twice the distance from the lamp
|
||||
return glm::vec4{static_cast<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
m_frame = Framestamp;
|
||||
m_wanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0};
|
||||
auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_slots.size()) || (size != m_lampssize))
|
||||
{
|
||||
allocate_lamps(count, size);
|
||||
}
|
||||
if (m_slots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_sceneryleft = setup(level).sceneryperframe;
|
||||
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
|
||||
// the lights come the most important first: the first ones within reach get the slots
|
||||
std::vector<gfx::local_lights::light const *> picked;
|
||||
for (auto const &light : Lights)
|
||||
{
|
||||
if (picked.size() >= m_slots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
picked.emplace_back(&light);
|
||||
}
|
||||
// lamps which already have a slot keep it
|
||||
std::vector<gfx::local_lights::light const *> unplaced;
|
||||
for (auto const *light : picked)
|
||||
{
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
if (glm::length(slot.position - light->position) > LAMP_MOVED)
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of lamps no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_slots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = lamp_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.part = light->part;
|
||||
slot.position = light->position;
|
||||
slot.range = light->range;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_wanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// the scene changed (events, the editor): the scenery parts are out of date
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
if (slot.scenery && (slot.version != Version))
|
||||
{
|
||||
slot.scenery = false;
|
||||
slot.vehicles = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::reset_lamps()
|
||||
{
|
||||
for (auto &slot : m_slots)
|
||||
{
|
||||
slot = lamp_slot{};
|
||||
}
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::scenery_wanted(std::size_t const Slot)
|
||||
{
|
||||
if (m_slots[Slot].scenery || (m_sceneryleft <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--m_sceneryleft;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_slots[Slot]};
|
||||
return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval));
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_slots[Slot]};
|
||||
auto const scenerycube{static_cast<int>(Slot)};
|
||||
auto const workingcube{static_cast<int>(m_slots.size() + Slot)};
|
||||
if (Vehicles)
|
||||
{
|
||||
// the vehicles go over the scenery
|
||||
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, workingcube * 6, m_lampssize, m_lampssize, 6);
|
||||
slot.vehicles = true;
|
||||
slot.vehiclesframe = Framestamp;
|
||||
++m_lampsvehicles;
|
||||
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Workingcube * 6, m_lampssize, m_lampssize, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
float const farthest{0.f};
|
||||
glClearTexSubImage(m_lampstexture, 0, 0, 0, scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
slot.scenery = true;
|
||||
slot.version = Version;
|
||||
slot.vehicles = false;
|
||||
++m_lampsscenery;
|
||||
glClearTexSubImage(m_lampstexture, 0, 0, 0, Scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
}
|
||||
set_layer((Vehicles ? workingcube : scenerycube) * 6);
|
||||
set_layer((Vehicles ? Workingcube : Scenerycube) * 6);
|
||||
setup_drawing(m_lampsframebuffer, m_lampssize);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end_lamp(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_lamp()
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
gl::framebuffer::unbind();
|
||||
set_layer(CAB_SLOT * 6);
|
||||
m_lampsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::lamps_timer_begin()
|
||||
void opengl46_local_shadows::gpu_lamps_timer(bool const Begin)
|
||||
{
|
||||
m_lampsframedrawcalls = 0;
|
||||
m_cpustart = std::chrono::steady_clock::now();
|
||||
m_lampstimer.begin();
|
||||
m_lampstiming = true;
|
||||
if (Begin)
|
||||
m_lampstimer.begin();
|
||||
else
|
||||
m_lampstimer.end();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::lamps_timer_end()
|
||||
void opengl46_local_shadows::gpu_begin_headlight(std::size_t const Slot)
|
||||
{
|
||||
if (false == m_lampstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_lampstimer.end();
|
||||
m_lampstiming = false;
|
||||
m_lampsdrawcalls = m_lampsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
|
||||
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
|
||||
{
|
||||
if (Light.owner == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
auto const &slot{m_slots[idx]};
|
||||
if ((slot.owner == Light.owner) && (slot.part == Light.part))
|
||||
{
|
||||
if (false == slot.scenery)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
Slot = idx;
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::lamp_weights(std::size_t const Slot) const
|
||||
{
|
||||
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
|
||||
return glm::vec4{static_cast<float>(cube), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
|
||||
{
|
||||
m_headlightswanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
auto const size{count > 0 ? setup(level).headlightsize : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
|
||||
{
|
||||
allocate_headlights(count, size);
|
||||
}
|
||||
if (m_headlightslots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// sets which already have a slot keep it
|
||||
std::vector<light_array::light_record const *> unplaced;
|
||||
for (auto const *light : Lights)
|
||||
{
|
||||
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of sets no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = headlight_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.index = light->index;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::reset_headlights()
|
||||
{
|
||||
for (auto &slot : m_headlightslots)
|
||||
{
|
||||
slot = headlight_slot{};
|
||||
}
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_headlightslots[Slot]};
|
||||
auto const level{setup(m_level)};
|
||||
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_headlightslots[Slot]};
|
||||
slot.drawn = true;
|
||||
slot.drawnframe = Framestamp;
|
||||
slot.projector = Projector;
|
||||
slot.rotation = Rotation;
|
||||
slot.projection = Projection;
|
||||
slot.direction = Direction;
|
||||
++m_headlightsdrawn;
|
||||
|
||||
gl::framebuffer::unbind();
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, static_cast<GLint>(Slot));
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_headlightsframebuffer);
|
||||
@@ -649,51 +289,17 @@ void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end_headlight(std::uint32_t const Drawcalls)
|
||||
void opengl46_local_shadows::gpu_end_headlight()
|
||||
{
|
||||
gl::framebuffer::unbind();
|
||||
m_headlightsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_begin()
|
||||
void opengl46_local_shadows::gpu_headlights_timer(bool const Begin)
|
||||
{
|
||||
m_headlightsframedrawcalls = 0;
|
||||
m_headlightscpustart = std::chrono::steady_clock::now();
|
||||
m_headlightstimer.begin();
|
||||
m_headlightstiming = true;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_end()
|
||||
{
|
||||
if (false == m_headlightstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_headlightstimer.end();
|
||||
m_headlightstiming = false;
|
||||
m_headlightsdrawcalls = m_headlightsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
|
||||
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
int opengl46_local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
|
||||
{
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
auto const &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == Owner) && (slot.index == Index))
|
||||
{
|
||||
return (slot.drawn ? static_cast<int>(idx) : -1);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::headlight_params(std::size_t const Slot) const
|
||||
{
|
||||
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
|
||||
auto const &projection{m_headlightslots[Slot].projection};
|
||||
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
|
||||
if (Begin)
|
||||
m_headlightstimer.begin();
|
||||
else
|
||||
m_headlightstimer.end();
|
||||
}
|
||||
|
||||
std::string opengl46_local_shadows::info_times()
|
||||
@@ -707,67 +313,3 @@ std::string opengl46_local_shadows::info_times()
|
||||
text += " headlight shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string opengl46_local_shadows::info() const
|
||||
{
|
||||
std::string text;
|
||||
if (m_size != 0)
|
||||
{
|
||||
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
|
||||
" ms";
|
||||
}
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto withvehicles{0};
|
||||
auto ready{0};
|
||||
for (auto const idx : m_wanted)
|
||||
{
|
||||
ready += (m_slots[idx].scenery ? 1 : 0);
|
||||
withvehicles += (m_slots[idx].vehicles ? 1 : 0);
|
||||
}
|
||||
text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " +
|
||||
std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " +
|
||||
std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms";
|
||||
}
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto ready{0};
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
ready += (m_headlightslots[idx].drawn ? 1 : 0);
|
||||
}
|
||||
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
|
||||
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
|
||||
to_string(m_headlightscputime, 2) + " ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::draw_debug_ui()
|
||||
{
|
||||
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
|
||||
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
|
||||
if (m_size != 0)
|
||||
{
|
||||
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
|
||||
}
|
||||
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
|
||||
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);
|
||||
ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f);
|
||||
if (false == m_slots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.find("\nscenery lamp shadows:")};
|
||||
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
|
||||
if (line != std::string::npos)
|
||||
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
|
||||
}
|
||||
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
|
||||
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.rfind('\n')};
|
||||
ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,37 +19,17 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/common/locallights.h"
|
||||
#include "rendering/common/localshadows.h"
|
||||
#include "rendering/lightarray.h"
|
||||
|
||||
class TDynamicObject;
|
||||
class TModel3d;
|
||||
|
||||
// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows):
|
||||
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
|
||||
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
|
||||
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
|
||||
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
|
||||
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
|
||||
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
|
||||
// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there
|
||||
// are any by the lamp. the shading reads the working copy then, the scenery part otherwise.
|
||||
// the six faces of a map are drawn at once, by a geometry shader (shadowcube.vert/.geom), so a model costs one draw call per
|
||||
// submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow() in
|
||||
// light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x, from the array of scenery
|
||||
// lamps when its linear is 1 and from the one of the cab lamp otherwise.
|
||||
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
|
||||
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle, see
|
||||
// opengl46_renderer::Headlight_Projector()), with what is within Global.gfx_lights_shadows_headlights_range ahead of the
|
||||
// vehicle, but not the vehicle itself. the map keeps the projector it was drawn with, in world space: one a few frames old is
|
||||
// still right for what doesn't move. read by headlight_beam_shadow() in light_common.glsl, for the headlights with the map in their
|
||||
// headlight_weights.w (1 + the layer) and the projection to it in gl::scene_ubs::headlight_shadow
|
||||
class opengl46_local_shadows
|
||||
// the shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows): the maps and the drawing to them of
|
||||
// gfx::local_shadows, which tells what they hold. the six faces of a cube map are drawn at once, by a geometry shader
|
||||
// (shadowcube.vert/.geom)
|
||||
class opengl46_local_shadows : public gfx::local_shadows
|
||||
{
|
||||
public:
|
||||
opengl46_local_shadows() = default;
|
||||
~opengl46_local_shadows();
|
||||
opengl46_local_shadows(const opengl46_local_shadows &) = delete;
|
||||
opengl46_local_shadows &operator=(const opengl46_local_shadows &) = delete;
|
||||
~opengl46_local_shadows() override;
|
||||
|
||||
// creates the programs and the stand-in maps. throws gl::shader_exception
|
||||
void init();
|
||||
@@ -67,157 +47,26 @@ class opengl46_local_shadows
|
||||
{
|
||||
return Alpha ? m_alpha_multidraw_program.get() : m_multidraw_program.get();
|
||||
}
|
||||
|
||||
// the cab lamp
|
||||
|
||||
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
|
||||
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
|
||||
// the size of the level when the level changed
|
||||
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
|
||||
// sets up the drawing to the map: the framebuffer, viewport, cleared faces, the state of the faces. the geometry is to be
|
||||
// drawn with program() and a modelview from the space of the model to the space of the map
|
||||
void begin();
|
||||
// ends the drawing to the map, leaves the default framebuffer bound and the culling of faces on
|
||||
void end(std::uint32_t const Drawcalls);
|
||||
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
|
||||
bool ready(TDynamicObject const *Vehicle) const
|
||||
{
|
||||
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
|
||||
}
|
||||
// position of the lamp the map was drawn for, in the space of the cab model
|
||||
glm::vec3 const &lamp() const
|
||||
{
|
||||
return m_lamp;
|
||||
}
|
||||
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
|
||||
// radius of the filter in texels
|
||||
glm::vec4 weights() const;
|
||||
|
||||
// lamps of scenery models
|
||||
|
||||
// a lamp with a slot
|
||||
struct lamp_slot
|
||||
{
|
||||
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
|
||||
void const *part{nullptr};
|
||||
glm::dvec3 position{0.0}; // world space
|
||||
float range{0.f}; // of the light, metres
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
|
||||
bool scenery{false}; // the scenery part of the map is drawn
|
||||
std::uint64_t version{0}; // of the scene the scenery part was drawn for
|
||||
bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads
|
||||
unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in
|
||||
};
|
||||
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
|
||||
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
|
||||
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
|
||||
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
|
||||
void reset_lamps();
|
||||
// slots of the lamps picked in this frame
|
||||
std::vector<std::size_t> const &lamps() const
|
||||
{
|
||||
return m_wanted;
|
||||
}
|
||||
lamp_slot const &slot(std::size_t const Slot) const
|
||||
{
|
||||
return m_slots[Slot];
|
||||
}
|
||||
// whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level
|
||||
bool scenery_wanted(std::size_t const Slot);
|
||||
// whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level)
|
||||
bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const;
|
||||
// the slot has no vehicles by it: the shading reads the scenery part
|
||||
void no_vehicles(std::size_t const Slot)
|
||||
{
|
||||
m_slots[Slot].vehicles = false;
|
||||
}
|
||||
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
|
||||
// the geometry is to be drawn with program() and a modelview to world axes centred on the lamp
|
||||
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a lamp
|
||||
void end_lamp(std::uint32_t const Drawcalls);
|
||||
// gpu timer of the drawing of the maps of the lamps in this frame
|
||||
void lamps_timer_begin();
|
||||
void lamps_timer_end();
|
||||
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
|
||||
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
|
||||
// the headlight_weights of the light of the lamp in the slot, see weights()
|
||||
glm::vec4 lamp_weights(std::size_t const Slot) const;
|
||||
|
||||
// headlights of vehicles
|
||||
|
||||
// a set of headlights (one end of a vehicle) with a slot
|
||||
struct headlight_slot
|
||||
{
|
||||
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
|
||||
int index{-1};
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
|
||||
bool drawn{false}; // the map holds the shadows of the headlights
|
||||
unsigned int drawnframe{0};
|
||||
// the projector the map was drawn with: world space position, world axes to its axes, projection
|
||||
glm::dvec3 projector{0.0};
|
||||
glm::mat3 rotation{1.f};
|
||||
glm::mat4 projection{1.f};
|
||||
glm::vec3 direction{0.f}; // of the light when the map was drawn
|
||||
};
|
||||
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
|
||||
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
|
||||
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
|
||||
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
|
||||
void reset_headlights();
|
||||
// slots of the headlights picked in this frame
|
||||
std::vector<std::size_t> const &headlights() const
|
||||
{
|
||||
return m_headlightswanted;
|
||||
}
|
||||
headlight_slot const &headlight(std::size_t const Slot) const
|
||||
{
|
||||
return m_headlightslots[Slot];
|
||||
}
|
||||
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
|
||||
// while the headlights Moved (with the vehicle)
|
||||
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
|
||||
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
|
||||
// to be drawn by the shadow pass with this projection, back faces only
|
||||
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a set of headlights, leaves the default framebuffer bound
|
||||
void end_headlight(std::uint32_t const Drawcalls);
|
||||
// gpu timer of the drawing of the maps of the headlights in this frame
|
||||
void headlights_timer_begin();
|
||||
void headlights_timer_end();
|
||||
// the slot of the headlights with their map ready in this frame, -1 if there is none
|
||||
int headlight_map(TDynamicObject const *Owner, int const Index) const;
|
||||
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
|
||||
glm::vec4 headlight_params(std::size_t const Slot) const;
|
||||
|
||||
// lines for the gpu times and statistics of the debug panel
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
std::string info() const;
|
||||
// controls for the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
protected:
|
||||
bool gpu_allocate(int const Size) override;
|
||||
void gpu_release() override;
|
||||
bool gpu_allocate_lamps(int const Count, int const Size) override;
|
||||
void gpu_release_lamps() override;
|
||||
bool gpu_allocate_headlights(int const Count, int const Size) override;
|
||||
void gpu_release_headlights() override;
|
||||
void gpu_begin_cab() override;
|
||||
void gpu_end_cab() override;
|
||||
void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override;
|
||||
void gpu_end_lamp() override;
|
||||
void gpu_begin_headlight(std::size_t const Slot) override;
|
||||
void gpu_end_headlight() override;
|
||||
void gpu_lamps_timer(bool const Begin) override;
|
||||
void gpu_headlights_timer(bool const Begin) override;
|
||||
|
||||
private:
|
||||
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
|
||||
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
|
||||
// it doesn't) of the maps of headlights
|
||||
struct level_setup
|
||||
{
|
||||
int size;
|
||||
unsigned int interval;
|
||||
float radius;
|
||||
int sceneryperframe;
|
||||
int headlightsize;
|
||||
unsigned int headlightmoving;
|
||||
unsigned int headlightstill;
|
||||
};
|
||||
static level_setup setup(int const Level);
|
||||
void allocate(int const Size);
|
||||
void release();
|
||||
void allocate_lamps(int const Count, int const Size);
|
||||
void release_lamps();
|
||||
void allocate_headlights(int const Count, int const Size);
|
||||
void release_headlights();
|
||||
void set_layer(int const Layer);
|
||||
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
@@ -226,48 +75,16 @@ class opengl46_local_shadows
|
||||
std::unique_ptr<gl::program> m_alpha_multidraw_program;
|
||||
GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth
|
||||
GLuint m_emptyheadlights{0}; // the same, a 2d array of one 1x1 layer
|
||||
int m_level{-1}; // level the maps were made for
|
||||
// cab lamp
|
||||
GLuint m_texture{0}; // cube map array, one cube
|
||||
// cab lamp: cube map array, one cube
|
||||
GLuint m_texture{0};
|
||||
GLuint m_framebuffer{0};
|
||||
int m_size{0}; // of a face of m_texture, 0 when there is none
|
||||
bool m_valid{false}; // m_texture holds the map of m_vehicle
|
||||
TDynamicObject const *m_vehicle{nullptr};
|
||||
TModel3d const *m_cab{nullptr};
|
||||
glm::vec3 m_lamp{0.f};
|
||||
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
|
||||
gl::gpu_timer m_timer;
|
||||
// scenery lamps: cube map array, the scenery parts of the slots first, then their working copies
|
||||
GLuint m_lampstexture{0};
|
||||
GLuint m_lampsframebuffer{0};
|
||||
int m_lampssize{0};
|
||||
std::vector<lamp_slot> m_slots;
|
||||
std::vector<std::size_t> m_wanted;
|
||||
unsigned int m_frame{0}; // framestamp of the last update_lamps()
|
||||
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
|
||||
gl::gpu_timer m_lampstimer;
|
||||
bool m_lampstiming{false};
|
||||
// headlights: 2d array, a layer per slot
|
||||
GLuint m_headlightstexture{0};
|
||||
GLuint m_headlightsframebuffer{0};
|
||||
int m_headlightssize{0};
|
||||
std::vector<headlight_slot> m_headlightslots;
|
||||
std::vector<std::size_t> m_headlightswanted;
|
||||
gl::gpu_timer m_headlightstimer;
|
||||
bool m_headlightstiming{false};
|
||||
std::chrono::steady_clock::time_point m_headlightscpustart;
|
||||
// statistics
|
||||
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
||||
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
|
||||
double m_cputime{0.0}; // milliseconds, smoothed
|
||||
std::chrono::steady_clock::time_point m_cpustart;
|
||||
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far
|
||||
std::uint32_t m_lampsvehicles{0}; // working copies drawn so far
|
||||
std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
|
||||
std::uint32_t m_lampsframedrawcalls{0};
|
||||
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
|
||||
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
|
||||
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
|
||||
std::uint32_t m_headlightsframedrawcalls{0};
|
||||
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -216,14 +216,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// the gpu culling of the instances, models and sleepers, the depth pass of the whole scene
|
||||
void walk_region_begin() override;
|
||||
void walk_editor_cursor() override;
|
||||
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area) override;
|
||||
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) override;
|
||||
bool walk_queue_model(TAnimModel *Instance, bool const Inrange) override;
|
||||
bool walk_queue_sleepers(TTrack *Track) override;
|
||||
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) override;
|
||||
bool walk_instances_begin() override;
|
||||
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) override;
|
||||
void walk_instances_end() override;
|
||||
gfx::geometrybank_manager &walk_geometry() override;
|
||||
bool walk_blending_enabled() const override;
|
||||
|
||||
// types
|
||||
struct viewport_config {
|
||||
@@ -346,11 +342,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// materials like the main view. no occlusion test, no depth prepass, one phase
|
||||
void Render_Instances_Indirect_Pass();
|
||||
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
|
||||
// (m_indirectdraws, m_indirectcommands), puts them in order for multi draws and has Cull fill in their commands
|
||||
// (m_indirectdraws, m_indirectcommands; Walk_Indirect_Draws()), puts them in order for multi draws and has Cull fill in their
|
||||
// commands
|
||||
void Record_Indirect_Draws( opengl46_instance_cull &Cull );
|
||||
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
|
||||
// is kept for the rest of the pass
|
||||
bool Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket );
|
||||
// the culling the pass under way feeds (Begin_Pass_Cull()), always one of this renderer
|
||||
opengl46_instance_cull &Pass_Cull() { return static_cast<opengl46_instance_cull &>( *m_passcull ); }
|
||||
// draws of the instances made, and the gl calls which made them
|
||||
struct indirect_counts {
|
||||
std::uint32_t draws { 0 };
|
||||
@@ -369,18 +365,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
|
||||
// the recorded draws of a shadow pass with the shadow programs, the way Bind_Material_Shadow() picks them
|
||||
indirect_counts Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set );
|
||||
// the texture Bind_Material_Shadow() binds for the alpha test of a material, or null_handle (no test)
|
||||
texture_handle Indirect_Shadow_Texture( material_handle const Material );
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
|
||||
// same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in
|
||||
// the buffer of the frame (m_drawblockstream), in the same order
|
||||
void Order_Indirect_Draws();
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw), put in order by Order_Indirect_Draws(): places their
|
||||
// model blocks in the buffer of the frame (m_drawblockstream), in the same order
|
||||
void Upload_Indirect_Blocks();
|
||||
// binds the model blocks of Count recorded draws from First for the programs of the multi draws (MODEL_DRAW_SSBO_BINDING)
|
||||
void Bind_Indirect_Blocks( std::size_t const First, std::size_t const Count );
|
||||
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
|
||||
// bank, primitive and blending
|
||||
bool Indirect_Mergeable( std::size_t const First, std::size_t const Other ) const;
|
||||
// the program Bind_Material() binds for the material, nullptr when it binds none
|
||||
gl::program const *Indirect_Program( material_handle Material ) const;
|
||||
// the variant of a material program for multi draws (built with MODEL_DRAWS), made the first time it's asked for.
|
||||
@@ -431,14 +420,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas );
|
||||
// whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry
|
||||
bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
|
||||
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
|
||||
// Render_Sleepers(), making their records first when they're missing or out of date
|
||||
void Queue_Sleepers( TTrack *Track );
|
||||
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
|
||||
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
|
||||
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
|
||||
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
|
||||
bool Queue_Model( TAnimModel *Instance, bool const Inrange );
|
||||
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
|
||||
void Update_Texture_Release();
|
||||
// textures used by materials of the sub-models of specified model, replaceable skins excluded
|
||||
@@ -566,7 +547,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared
|
||||
// with the one of the cpu (2)
|
||||
opengl46_instance_cull m_instancecull;
|
||||
bool m_instancecullpass { false }; // the current pass feeds a culling: m_passcull
|
||||
unsigned int m_instancecullframe { ~0u }; // frame it last ran in
|
||||
// the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows,
|
||||
// _reflections), with records and programs shared with m_instancecull
|
||||
@@ -578,42 +558,8 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::vector<std::unique_ptr<opengl46_instance_cull>> m_lampcull;
|
||||
opengl46_instance_cull *m_lightshadowcull { nullptr }; // the one of the map of headlights or of a lamp being drawn
|
||||
std::size_t m_lampscenerydrawn { 0 }; // scenery parts of the maps of lamps drawn in this frame so far
|
||||
opengl46_instance_cull *m_passcull { nullptr }; // the one the current pass feeds
|
||||
opengl46_instance_cull *m_indirectcull { &m_instancecull }; // the one the recorded draws (m_indirectdraws) were culled by
|
||||
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
|
||||
// Instances_Cast_Shadow() of the groups of the shadow pass under way
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
|
||||
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
|
||||
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
|
||||
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
|
||||
struct sleeper_records {
|
||||
scene::basic_cell::instance_bucket bucket; // the records only, no instances
|
||||
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
|
||||
scene::bounding_area area; // of the locations of the sleepers
|
||||
TModel3d const *model { nullptr }; // what the records were made from
|
||||
material_handle skin { null_handle };
|
||||
glm::mat4 const *transforms { nullptr };
|
||||
std::size_t count { 0 };
|
||||
unsigned int frame { 0 }; // queued last
|
||||
};
|
||||
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
|
||||
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
|
||||
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
|
||||
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
|
||||
struct model_records {
|
||||
scene::basic_cell::instance_bucket bucket; // the instance alone
|
||||
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
|
||||
scene::bounding_area area; // the location of the instance
|
||||
unsigned int frame { 0 }; // queued last
|
||||
};
|
||||
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
|
||||
bool m_modelspass { false }; // the models drawn on their own of the pass under way go to its gpu culling
|
||||
bool m_indirectcolourmodels { false }; // with m_indirectcolourpending: the depth pass culled and recorded them too
|
||||
bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
|
||||
bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
|
||||
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
|
||||
// drawn before the culling runs)
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
|
||||
// depth pyramid for the occlusion test of the gpu culling
|
||||
opengl46_hiz m_hiz;
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (Global.gfx_shadow_sdsm)
|
||||
@@ -621,75 +567,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
// with a cascade fitted: the corners of the area its map is to cover, for setup_pass()
|
||||
std::array<glm::vec3, 8> const *m_shadowfitarea { nullptr };
|
||||
// what the last shadow pass did, for the debug panel
|
||||
struct shadow_fit_state {
|
||||
bool fitted { false };
|
||||
char const *reason { nullptr }; // why not, with gfx.shadow.sdsm on
|
||||
unsigned int age { 0 }; // frames since the measurement
|
||||
float depthmin { 0.f };
|
||||
float depthmax { 0.f };
|
||||
float turn { 0.f }; // of the view since the measurement, degrees
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits {};
|
||||
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
|
||||
std::array<float, gl::MAX_CASCADES> slice {};
|
||||
std::array<float, gl::MAX_CASCADES> drawn {};
|
||||
} m_shadowfitstate;
|
||||
gfx::shadow_fit::fit_state m_shadowfitstate;
|
||||
// the sizes setup_pass() found for the last shadow pass: of the whole slice of the view and as drawn
|
||||
float m_shadowslicesize { 0.f };
|
||||
float m_shadowdrawnsize { 0.f };
|
||||
// a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be
|
||||
// replayed once the instances of its command are known: the state the walk left for it and what it bound
|
||||
struct indirect_draw {
|
||||
gl::model_ubs ubs;
|
||||
material_handle material;
|
||||
TSubModel const *submodel;
|
||||
gfx::geometry_handle geometry;
|
||||
unsigned int type; // primitive
|
||||
bool blending; // drawn with the transparency hack of submodels faded out
|
||||
std::uint32_t group { 0 }; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group())
|
||||
};
|
||||
std::vector<indirect_draw> m_indirectdraws;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
|
||||
// the walks Record_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the
|
||||
// draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it
|
||||
struct cascade_band {
|
||||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
struct cascade_record {
|
||||
unsigned int frame { 0 };
|
||||
std::vector<cascade_band> bands;
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
|
||||
std::vector<indirect_draw> m_cascadedraws;
|
||||
std::vector<gl::instance_draw_command> m_cascadecommands;
|
||||
unsigned int m_cascaderecordframe { 0 };
|
||||
// the walks Record_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
|
||||
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
|
||||
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
|
||||
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
|
||||
// own (with an instance in the key) are walked every time
|
||||
struct walk_band {
|
||||
std::vector<indirect_draw> draws; // as recorded
|
||||
float bandmin { 0.f };
|
||||
std::uint32_t modelversion { 0 };
|
||||
bool blending { false }; // the state the walk began in
|
||||
bool valid { false };
|
||||
};
|
||||
struct walk_record {
|
||||
unsigned int frame { 0 }; // used last
|
||||
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
|
||||
};
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
|
||||
int m_walkrankcutoff { 0 }; // Global.gfx_shadow_rank_cutoff the walks were made with
|
||||
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
|
||||
// of the walks and of putting the draws in order with their blocks uploaded
|
||||
struct walk_counts {
|
||||
std::uint32_t walked { 0 };
|
||||
std::uint32_t changing { 0 };
|
||||
std::uint32_t reused { 0 };
|
||||
double walktime { 0.0 };
|
||||
double ordertime { 0.0 };
|
||||
};
|
||||
// cpu time of the parts of the culling and the draws of the instances of the main view (Render_Instances_Indirect() and
|
||||
// _Pending()), for the panel: the depth of the recorded walk of the scene drawn before the culling, the depth pyramids, the
|
||||
// culling passes, the walks with putting the draws in order, the depth and the colour of the instances
|
||||
@@ -702,47 +583,13 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
double colour { 0.0 };
|
||||
};
|
||||
cull_parts m_cullparts;
|
||||
walk_counts m_walkcounts;
|
||||
walk_counts m_lastwalkcounts;
|
||||
unsigned int m_walkcountsframe { 0 };
|
||||
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
|
||||
void Replay_Walk( walk_band const &Walk, bool const Shadows );
|
||||
// multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where
|
||||
// the blocks are
|
||||
bool m_indirectmultidraw { false };
|
||||
GLuint m_indirectblockbuffer { 0 };
|
||||
GLintptr m_indirectblockoffset { 0 };
|
||||
std::uint64_t m_indirectblockgeneration { 0 };
|
||||
// working space of Order_Indirect_Draws() and Upload_Indirect_Blocks(), kept between frames
|
||||
struct indirect_queue {
|
||||
std::uint32_t next; // draw
|
||||
std::uint32_t end;
|
||||
std::uint32_t link; // next queue waiting for the same state
|
||||
};
|
||||
struct indirect_state {
|
||||
std::uint32_t head;
|
||||
std::uint32_t tail;
|
||||
};
|
||||
// what a multi draw has in common, and the draw which has it
|
||||
struct indirect_state_key {
|
||||
material_handle material;
|
||||
std::uint32_t variant; // of the textures of the material
|
||||
std::uint32_t bank;
|
||||
unsigned int type;
|
||||
std::uint32_t indexed;
|
||||
bool blending;
|
||||
std::uint32_t draw;
|
||||
auto tie() const {
|
||||
return std::tie( material, variant, bank, type, indexed, blending ); }
|
||||
};
|
||||
std::vector<indirect_state_key> m_indirectstatekeys;
|
||||
std::vector<std::uint32_t> m_indirectdrawstates;
|
||||
std::vector<indirect_queue> m_indirectqueues;
|
||||
std::vector<indirect_state> m_indirectstates;
|
||||
std::vector<std::uint32_t> m_indirectstateorder;
|
||||
std::vector<std::uint32_t> m_indirectorder;
|
||||
std::vector<indirect_draw> m_indirectdrawsordered;
|
||||
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
|
||||
// working space of Upload_Indirect_Blocks(), kept between frames
|
||||
std::vector<gl::model_ubs> m_indirectblocks;
|
||||
// the model blocks of the multi draws, one buffer for each of the last frames like model_ubo (nullptr: no multi draws)
|
||||
std::unique_ptr<gl::frame_stream> m_drawblockstream;
|
||||
@@ -754,10 +601,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
std::unique_ptr<gl::program> m_shadow_multidraw_shader; // and of the sun shadows
|
||||
std::unique_ptr<gl::program> m_alpha_shadow_multidraw_shader;
|
||||
bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws
|
||||
bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing
|
||||
std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded
|
||||
material_handle m_indirectmaterial { null_handle }; // what the last Bind_Material() bound while recording
|
||||
TSubModel const *m_indirectsubmodel { nullptr };
|
||||
// depth prepass: programs and the class of the material programs (see Prepass_Class())
|
||||
std::unique_ptr<gl::program> m_prepass_shader;
|
||||
std::unique_ptr<gl::program> m_prepass_alpha_shader;
|
||||
@@ -768,7 +611,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
bool m_scenedepthpass { false }; // inside it: Bind_Material() and draw() work for the depth
|
||||
bool m_scenedepthskip { true }; // the material bound last isn't drawn in it
|
||||
bool m_scenedepthdrawn { false }; // it was drawn for the colour pass under way, which skips what was done in it already
|
||||
bool m_indirectcolourpending { false }; // the instances were culled in it, the colour pass draws them from the recorded draws
|
||||
// with m_indirectcolourpending: the instances were culled in it, the colour pass draws them from the recorded draws
|
||||
opengl46_instance_cull::command_set m_indirectcolourset { opengl46_instance_cull::command_set::first }; // with these commands
|
||||
debug_stats m_scenedepthstats; // of its last run
|
||||
double m_scenedepthcputime { 0.0 }; // ms
|
||||
@@ -894,12 +737,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
|
||||
opengl46_local_shadows m_localshadows;
|
||||
// the shadow pass draws to a cube shadow map, with the programs of m_localshadows
|
||||
bool m_cubeshadows = false;
|
||||
// vehicles near the camera which cast the shadows of scenery lamps, and those of them by the lamp being drawn
|
||||
std::vector<TDynamicObject *> m_lampshadowvehicles;
|
||||
std::vector<TDynamicObject *> m_lampshadowslot;
|
||||
// headlights which may get shadow maps in this frame, and their importance
|
||||
std::vector<std::pair<float, light_array::light_record const *>> m_headlightshadowcandidates;
|
||||
std::vector<light_array::light_record const *> m_headlightshadowpicked;
|
||||
|
||||
double m_precipitationrotation;
|
||||
|
||||
|
||||
@@ -10,27 +10,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "rendering/opengl46/opengl46shadowfit.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
|
||||
// get any closer to the camera than this, however close the pixels are
|
||||
float const SPLIT_NEAR_MIN{0.5f};
|
||||
float const SPLIT_FAR_MIN{16.f};
|
||||
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
|
||||
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
|
||||
// has to be within its map
|
||||
float const MARGIN_METRES{1.f};
|
||||
float const MARGIN_SHARE{0.05f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_shadow_fit::~opengl46_shadow_fit()
|
||||
{
|
||||
release();
|
||||
@@ -97,20 +80,7 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
depth = m_depth;
|
||||
}
|
||||
|
||||
// numbers start from 1, 0 is no measurement
|
||||
if (++m_measurement == 0)
|
||||
++m_measurement;
|
||||
gl::shadow_fit_data data{};
|
||||
data.measurement = m_measurement;
|
||||
data.depthmin = std::numeric_limits<float>::infinity();
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
for (std::size_t axis = 0; axis < 4; ++axis)
|
||||
{
|
||||
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
|
||||
data.bounds[cascade * 8 + 4 + axis] = 0u;
|
||||
}
|
||||
}
|
||||
auto const data{begin_measurement(View, Frame)};
|
||||
m_buffer->update(data);
|
||||
m_buffer->bind_base();
|
||||
|
||||
@@ -124,11 +94,12 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
m_depthprogram->dispatch(groupsx, groupsy);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
|
||||
auto const bounds{bounds_params(View)};
|
||||
auto const margins{margin_params()};
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 0, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 4, 1, GL_FALSE, glm::value_ptr(viewtolight));
|
||||
glProgramUniform4f(*m_boundsprogram, 8, View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f));
|
||||
glProgramUniform4f(*m_boundsprogram, 9, MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f);
|
||||
glProgramUniform4f(*m_boundsprogram, 8, bounds.x, bounds.y, bounds.z, bounds.w);
|
||||
glProgramUniform4f(*m_boundsprogram, 9, margins.x, margins.y, margins.z, margins.w);
|
||||
m_boundsprogram->dispatch(groupsx, groupsy);
|
||||
// for the copy of the read back
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
@@ -139,27 +110,14 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
|
||||
m_timer.end();
|
||||
|
||||
m_readback->push(*m_buffer);
|
||||
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
|
||||
}
|
||||
|
||||
opengl46_shadow_fit::result const *opengl46_shadow_fit::latest()
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
if (m_readback && m_readback->pop(&data))
|
||||
{
|
||||
auto const &source{m_pending[data.measurement % PENDING]};
|
||||
if ((data.measurement != 0) && (source.measurement == data.measurement))
|
||||
m_latest = result{data, source.source, source.frame};
|
||||
}
|
||||
return m_latest ? &(*m_latest) : nullptr;
|
||||
}
|
||||
|
||||
float opengl46_shadow_fit::unorder(std::uint32_t const Value)
|
||||
{
|
||||
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
|
||||
float result;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
arrived(data);
|
||||
return gfx::shadow_fit::latest();
|
||||
}
|
||||
|
||||
std::string opengl46_shadow_fit::info_times()
|
||||
|
||||
@@ -19,34 +19,14 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/common/shadowfit.h"
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps, gfx.shadow.sdsm).
|
||||
// at the end of the colour pass of the main view two compute passes go over its resolved depth: the nearest and the farthest
|
||||
// distance of the pixels which aren't the sky (shadow_fit_depth.comp), then the distances split between the cascades and,
|
||||
// for each cascade, the box of the pixels it takes in the space of the light (shadow_fit_bounds.comp). the result comes back
|
||||
// to the cpu a frame or two later without waiting (gl::readback), with the camera and the light it was measured with, and
|
||||
// the renderer makes the cascades of the frame cover the boxes (opengl46_renderer::Shadow_Fit()) instead of the whole slices
|
||||
// of the view between the fixed distances of shadowtune
|
||||
class opengl46_shadow_fit
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm): the passes and the buffers of
|
||||
// gfx::shadow_fit, which tells what they do. the result comes back to the cpu a frame or two later without waiting
|
||||
// (gl::readback), and opengl46_renderer::Shadow_Fit() fits the cascades of the frame to it
|
||||
class opengl46_shadow_fit : public gfx::shadow_fit
|
||||
{
|
||||
public:
|
||||
// the view and the light a measurement was made with
|
||||
struct view
|
||||
{
|
||||
glm::dvec3 camera; // position of the camera
|
||||
glm::mat4 projection; // projection of the view
|
||||
glm::mat3 rotation; // world to view space, rotation only
|
||||
glm::mat3 light; // world to the space of the light, rotation only
|
||||
float range{0.f}; // the farthest distance the cascades may reach
|
||||
};
|
||||
// a measurement which came back
|
||||
struct result
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
view source;
|
||||
unsigned int frame{0}; // frame of the renderer it was made in
|
||||
};
|
||||
|
||||
opengl46_shadow_fit() = default;
|
||||
~opengl46_shadow_fit();
|
||||
opengl46_shadow_fit(const opengl46_shadow_fit &) = delete;
|
||||
@@ -63,30 +43,15 @@ class opengl46_shadow_fit
|
||||
// the time of the passes, for the debug panel
|
||||
std::string info_times();
|
||||
|
||||
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
|
||||
static float unorder(std::uint32_t const Value);
|
||||
|
||||
private:
|
||||
// the texture the depth is resolved to when there is no resolved depth to measure
|
||||
void allocate(int const Width, int const Height);
|
||||
void release();
|
||||
|
||||
// a measurement on its way back, kept by its number
|
||||
struct pending
|
||||
{
|
||||
std::uint32_t measurement{0}; // 0: none
|
||||
view source;
|
||||
unsigned int frame{0};
|
||||
};
|
||||
static constexpr std::size_t PENDING = 8; // more than the copies gl::readback keeps in flight
|
||||
|
||||
std::unique_ptr<gl::program> m_depthprogram;
|
||||
std::unique_ptr<gl::program> m_boundsprogram;
|
||||
std::unique_ptr<gl::ssbo> m_buffer;
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
std::array<pending, PENDING> m_pending;
|
||||
std::uint32_t m_measurement{0}; // number of the last measurement
|
||||
std::optional<result> m_latest;
|
||||
GLuint m_depth{0};
|
||||
GLuint m_depthfb{0};
|
||||
glm::ivec2 m_depthsize{0};
|
||||
|
||||
@@ -12,84 +12,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "environment/skydome.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
|
||||
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
|
||||
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
|
||||
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
|
||||
float const SH_Y00{0.282095f};
|
||||
float const SH_Y1{0.488603f};
|
||||
float const PI{3.14159265359f};
|
||||
|
||||
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
|
||||
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
|
||||
{
|
||||
return {SH_Y00,
|
||||
SH_Y1 * n.y,
|
||||
SH_Y1 * n.z,
|
||||
SH_Y1 * n.x,
|
||||
1.092548f * n.x * n.y,
|
||||
1.092548f * n.y * n.z,
|
||||
0.315392f * (3.f * n.z * n.z - 1.f),
|
||||
1.092548f * n.x * n.z,
|
||||
0.546274f * (n.x * n.x - n.y * n.y)};
|
||||
}
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
|
||||
{
|
||||
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
|
||||
}
|
||||
|
||||
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
|
||||
glm::vec3 sky_shoulder(glm::vec3 const &Color)
|
||||
{
|
||||
float const knee{0.25f};
|
||||
float const range{0.95f - knee};
|
||||
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
|
||||
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
|
||||
}
|
||||
|
||||
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
|
||||
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
|
||||
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
|
||||
// renderer, see the notes of the plan, stage S2)
|
||||
float const ATMOSPHERE_SKY_SCALE{16.f};
|
||||
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
|
||||
float const ATMOSPHERE_SUN_SCALE{1.45f};
|
||||
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
|
||||
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
|
||||
float const ATMOSPHERE_FADE_START{-6.f};
|
||||
float const ATMOSPHERE_FADE_END{10.f};
|
||||
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
|
||||
// ground, in the units of the ambient light, is the diffuse colour times their ratio
|
||||
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
|
||||
|
||||
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
|
||||
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
|
||||
float const RAYLEIGH_SCALE_HEIGHT{8.f};
|
||||
float const MIE_SCATTERING{3.996e-3f};
|
||||
float const MIE_EXTINCTION{4.440e-3f};
|
||||
float const MIE_SCALE_HEIGHT{1.2f};
|
||||
float const MIE_ASYMMETRY{0.8f};
|
||||
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
|
||||
float const OZONE_ALTITUDE{25.f};
|
||||
float const OZONE_HALF_WIDTH{15.f};
|
||||
float const BOTTOM_RADIUS{6360.f};
|
||||
float const TOP_RADIUS{6460.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_sky::~opengl46_sky()
|
||||
{
|
||||
@@ -137,15 +63,7 @@ void opengl46_sky::init()
|
||||
m_skyview_table = create_table(gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
|
||||
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
|
||||
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
|
||||
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
|
||||
m_atmosphere.top_radius = TOP_RADIUS;
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->update(m_light.atmosphere());
|
||||
}
|
||||
|
||||
void opengl46_sky::bind()
|
||||
@@ -155,75 +73,13 @@ void opengl46_sky::bind()
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
}
|
||||
|
||||
opengl46_sky::sh_coefficients opengl46_sky::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
|
||||
{
|
||||
sh_coefficients radiance{};
|
||||
auto const &vertices{Skydome.vertices()};
|
||||
auto const &colors{Skydome.colors()};
|
||||
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
|
||||
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
|
||||
float totalweight{0.f};
|
||||
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
|
||||
{
|
||||
auto direction{glm::normalize(vertices[i])};
|
||||
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
|
||||
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
|
||||
// both are undone here, so the light comes from the sky the Perez model describes
|
||||
auto const height{std::max(direction.y, 0.05f)};
|
||||
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
|
||||
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
|
||||
direction.y = std::max(direction.y, 0.f);
|
||||
direction = glm::normalize(direction);
|
||||
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
|
||||
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
|
||||
auto const basis{sh_basis(direction)};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
radiance[c] += color * (basis[c] * weight);
|
||||
totalweight += weight;
|
||||
}
|
||||
if (totalweight <= 0.f)
|
||||
return {};
|
||||
for (auto &coefficient : radiance)
|
||||
coefficient *= 2.f * PI / totalweight;
|
||||
|
||||
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
|
||||
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
|
||||
// which converts the sunlight to them
|
||||
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
|
||||
glm::vec3 skyup{0.f};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
|
||||
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
|
||||
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
|
||||
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
|
||||
radiance[1] += groundradiance * (SH_Y1 * -PI);
|
||||
|
||||
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
|
||||
sh_coefficients directional{};
|
||||
glm::vec3 const average{radiance[0] * SH_Y00};
|
||||
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
|
||||
for (std::size_t c = 1; c < radiance.size(); ++c)
|
||||
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
|
||||
return directional;
|
||||
}
|
||||
|
||||
void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
{
|
||||
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
|
||||
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
|
||||
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
|
||||
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
|
||||
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
|
||||
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
|
||||
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
|
||||
if (!mediumchanged && !sunchanged)
|
||||
bool mediumchanged{false};
|
||||
if (false == m_light.atmosphere_changed(Sundirection, mediumchanged))
|
||||
return;
|
||||
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->update(m_light.atmosphere());
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
|
||||
@@ -257,89 +113,23 @@ void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_result_readback.push(*m_atmosphere_result_ssbo);
|
||||
m_tables_medium = medium;
|
||||
m_tables_sun = Sundirection;
|
||||
}
|
||||
|
||||
void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
|
||||
{
|
||||
// the light the simulation set, which is kept where the atmosphere is not used
|
||||
glm::vec3 const oldambient{Sunlight.ambient};
|
||||
glm::vec3 const olddiffuse{Sunlight.diffuse};
|
||||
glm::vec3 const oldfog{Fogcolor};
|
||||
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
|
||||
|
||||
m_light.begin_update();
|
||||
// atmosphere
|
||||
m_weight = 0.f;
|
||||
if (Global.gfx_atmosphere)
|
||||
{
|
||||
update_atmosphere(glm::normalize(Sundirection));
|
||||
gl::atmosphere_result result;
|
||||
if (m_result_readback.pop(&result))
|
||||
m_result = result;
|
||||
if (m_result)
|
||||
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
|
||||
}
|
||||
|
||||
sh_coefficients directional{};
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
// the dome changes with the sun, the weather and the light the simulation works out from them
|
||||
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
|
||||
if (!m_dome_key || *m_dome_key != key)
|
||||
{
|
||||
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
|
||||
m_dome_key = key;
|
||||
}
|
||||
directional = m_dome_sh;
|
||||
}
|
||||
|
||||
m_sunlight = olddiffuse;
|
||||
m_ambient = oldambient;
|
||||
m_fog = oldfog;
|
||||
m_sun_disc_color = glm::vec3{1.f};
|
||||
if (m_weight > 0.f)
|
||||
{
|
||||
auto const &result{*m_result};
|
||||
// the camera's white balance: the sun high in the sky is white
|
||||
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
|
||||
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
|
||||
|
||||
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
|
||||
// with the shadows of the clouds the sun is held back where they are, not everywhere
|
||||
auto const overcastsun{m_cloud_shadows ? 1.f : (1.f - 0.65f * m_overcast)};
|
||||
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * overcastsun};
|
||||
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
|
||||
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
|
||||
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
|
||||
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
|
||||
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
|
||||
|
||||
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
|
||||
m_ambient = glm::mix(oldambient, ambient, m_weight);
|
||||
m_fog = glm::mix(oldfog, fog, m_weight);
|
||||
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
|
||||
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
|
||||
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
auto const evenness{1.f - 0.75f * m_overcast};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
{
|
||||
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
|
||||
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
|
||||
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
|
||||
Fogcolor = m_fog;
|
||||
m_light.set_result(result);
|
||||
}
|
||||
m_light.end_update(Sunlight, Fogcolor, Skydome, Sundirection, Sunangle);
|
||||
|
||||
// directional part of the ambient light for the shading
|
||||
gl::sky_data skydata{};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
|
||||
auto const &skydata{m_light.sky_data()};
|
||||
if (!m_sky_data_uploaded || std::memcmp(&skydata, &m_sky_data, sizeof(skydata)) != 0)
|
||||
{
|
||||
m_sky_data = skydata;
|
||||
@@ -351,14 +141,12 @@ void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDom
|
||||
void opengl46_sky::setup_sky(gl::model_ubs &Model) const
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, m_skyview_table);
|
||||
// the volumetric clouds make the overcast sky themselves
|
||||
Model.param[0] = glm::vec4{m_weight, m_sky_scale, m_clouds ? 0.f : 0.75f * m_overcast, m_sky_flat_luminance};
|
||||
Model.param[1] = glm::vec4{m_white_balance, 0.f};
|
||||
m_light.sky_params(Model.param[0], Model.param[1]);
|
||||
}
|
||||
|
||||
glm::vec3 opengl46_sky::sun_disc_color(glm::vec3 const &Oldcolor) const
|
||||
{
|
||||
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
|
||||
return m_light.sun_disc_color(Oldcolor);
|
||||
}
|
||||
|
||||
void opengl46_sky::prefilter(gl::cubemap const &Source)
|
||||
@@ -402,19 +190,5 @@ std::string opengl46_sky::info_times()
|
||||
|
||||
void opengl46_sky::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
|
||||
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
|
||||
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
|
||||
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
|
||||
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
|
||||
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
|
||||
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
|
||||
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
|
||||
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
|
||||
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
|
||||
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
|
||||
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
|
||||
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
|
||||
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
|
||||
m_light.draw_debug_ui();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
#include "rendering/common/skylight.h"
|
||||
|
||||
class CSkyDome;
|
||||
|
||||
@@ -32,7 +33,8 @@ class CSkyDome;
|
||||
// them (sky_atmosphere.frag), and the sunlight, ambient light and fog colour read back from them a few frames later.
|
||||
// these are values for the render only: the simulation (Global.DayLight, fLuminance) keeps its own
|
||||
// - the environment map prefiltered with the GGX lobe, for rough reflections (envmap_prefilter.comp)
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before.
|
||||
// what isn't the work of the gpu is the light of the sky shared with the vulkan renderer (gfx::sky_light)
|
||||
class opengl46_sky
|
||||
{
|
||||
public:
|
||||
@@ -52,7 +54,7 @@ class opengl46_sky
|
||||
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
|
||||
float atmosphere_weight() const
|
||||
{
|
||||
return m_weight;
|
||||
return m_light.atmosphere_weight();
|
||||
}
|
||||
// the sky-view table of the atmosphere, its white balance, brightness and the overcast it was drawn with, for the colours of
|
||||
// the fog (opengl46_weather)
|
||||
@@ -62,22 +64,21 @@ class opengl46_sky
|
||||
}
|
||||
glm::vec3 const &white_balance() const
|
||||
{
|
||||
return m_white_balance;
|
||||
return m_light.white_balance();
|
||||
}
|
||||
float sky_scale() const
|
||||
{
|
||||
return m_sky_scale;
|
||||
return m_light.sky_scale();
|
||||
}
|
||||
float overcast() const
|
||||
{
|
||||
return m_overcast;
|
||||
return m_light.overcast();
|
||||
}
|
||||
// the volumetric clouds are drawn over the sky (it doesn't take the overcast look then), and their shadows hold back the
|
||||
// sunlight (it isn't dimmed by the overcast then)
|
||||
void set_clouds(bool const Clouds, bool const Shadows)
|
||||
{
|
||||
m_clouds = Clouds;
|
||||
m_cloud_shadows = Shadows;
|
||||
m_light.set_clouds(Clouds, Shadows);
|
||||
}
|
||||
// sets the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag) and binds its table
|
||||
void setup_sky(gl::model_ubs &Model) const;
|
||||
@@ -96,11 +97,6 @@ class opengl46_sky
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
// spherical harmonics of order 2, rgb
|
||||
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
|
||||
|
||||
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
|
||||
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
|
||||
// sets up the parameters of the atmosphere for the frame and runs the passes whose input changed
|
||||
void update_atmosphere(glm::vec3 const &Sundirection);
|
||||
// creates an immutable 2d texture for a look-up table
|
||||
@@ -121,31 +117,11 @@ class opengl46_sky
|
||||
GLuint m_transmittance_table{0};
|
||||
GLuint m_multiscattering_table{0};
|
||||
GLuint m_skyview_table{0};
|
||||
gl::atmosphere_ubs m_atmosphere{};
|
||||
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
|
||||
// the sky-view table also on the sun
|
||||
std::optional<glm::vec4> m_tables_medium;
|
||||
std::optional<glm::vec3> m_tables_sun;
|
||||
gl::readback m_result_readback{sizeof(gl::atmosphere_result)};
|
||||
std::optional<gl::atmosphere_result> m_result;
|
||||
gl::gpu_timer m_atmosphere_timer;
|
||||
|
||||
// what update() made of the frame, for the sky, the sun disc and the debug window
|
||||
float m_weight{0.f};
|
||||
glm::vec3 m_white_balance{1.f};
|
||||
float m_sky_scale{0.f};
|
||||
float m_overcast{0.f};
|
||||
bool m_clouds{false};
|
||||
bool m_cloud_shadows{false};
|
||||
float m_sky_flat_luminance{0.f};
|
||||
glm::vec3 m_sun_disc_color{1.f};
|
||||
glm::vec3 m_sunlight{0.f};
|
||||
glm::vec3 m_ambient{0.f};
|
||||
glm::vec3 m_fog{0.f};
|
||||
|
||||
// cache of the directional ambient light of the dome, remade when its inputs change
|
||||
std::optional<std::array<glm::vec4, 3>> m_dome_key;
|
||||
sh_coefficients m_dome_sh{};
|
||||
// the light of the sky, shared with the vulkan renderer
|
||||
gfx::sky_light m_light;
|
||||
|
||||
// prefiltered environment map
|
||||
std::unique_ptr<gl::program> m_prefilter_program;
|
||||
|
||||
@@ -17,13 +17,6 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr initial_capacity{8};
|
||||
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
|
||||
float constexpr default_repeat{8.f};
|
||||
|
||||
// array textures: diffuse, normal maps, specular/gloss maps
|
||||
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
|
||||
|
||||
void bind_for_upload(opengl_texture const &Texture)
|
||||
{
|
||||
::glActiveTexture(GL_TEXTURE0);
|
||||
@@ -38,11 +31,8 @@ void set_anisotropy(GLenum const Target)
|
||||
|
||||
} // namespace
|
||||
|
||||
bool opengl46_terrain_materials::init()
|
||||
bool opengl46_terrain_materials::gpu_init()
|
||||
{
|
||||
if (m_state != 0)
|
||||
return m_state > 0;
|
||||
m_state = -1;
|
||||
try
|
||||
{
|
||||
m_copy = std::make_unique<gl::postfx>(gl::shader("copy.frag"));
|
||||
@@ -53,151 +43,56 @@ bool opengl46_terrain_materials::init()
|
||||
return false;
|
||||
}
|
||||
m_framebuffer = std::make_unique<gl::framebuffer>();
|
||||
|
||||
m_white = make_texture("internal_src:terrain/white", GL_TEXTURE_2D);
|
||||
{
|
||||
auto const &white{m_textures.texture(m_white)};
|
||||
std::uint8_t const texel[4]{255, 255, 255, 255};
|
||||
bind_for_upload(white);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
|
||||
}
|
||||
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
{
|
||||
auto &array{m_arrays[i]};
|
||||
array.handle = make_texture(names[i], GL_TEXTURE_2D_ARRAY);
|
||||
array.size = array_sizes[i];
|
||||
array.srgb = (i == 0);
|
||||
array.format = (array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8);
|
||||
make_storage(array, initial_capacity);
|
||||
}
|
||||
m_capacity = initial_capacity;
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
|
||||
try
|
||||
{
|
||||
m_program = GfxRenderer->Fetch_Shader("terrain");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
texture_handle opengl46_terrain_materials::make_texture(std::string const &Name, GLenum const Target)
|
||||
void opengl46_terrain_materials::gpu_texture(texture_source &Texture, texture_kind const Kind)
|
||||
{
|
||||
auto const handle{m_textures.create(Name, false, GL_RGBA)};
|
||||
auto &texture{m_textures.texture(handle)};
|
||||
auto &texture{static_cast<opengl_texture &>(Texture)};
|
||||
if (texture.id == opengl_texture::invalid_id)
|
||||
::glGenTextures(1, &texture.id);
|
||||
texture.target = Target;
|
||||
// the content is supplied here, the texture manager isn't to load, release or replace it
|
||||
texture.is_ready = true;
|
||||
texture.is_static = true;
|
||||
texture.is_rendertarget = true;
|
||||
texture.is_texstub = false;
|
||||
texture.data_state = resource_state::good;
|
||||
return handle;
|
||||
texture.target = (Kind == texture_kind::layers ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::make_storage(texture_array &Array, int const Layers)
|
||||
void opengl46_terrain_materials::gpu_white(texture_source &Texture)
|
||||
{
|
||||
auto &texture{m_textures.texture(Array.handle)};
|
||||
auto const &white{static_cast<opengl_texture &>(Texture)};
|
||||
std::uint8_t const texel[4]{255, 255, 255, 255};
|
||||
bind_for_upload(white);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh)
|
||||
{
|
||||
auto &texture{m_gltextures.texture(Array.handle)};
|
||||
if (Fresh)
|
||||
{
|
||||
::glDeleteTextures(1, &texture.id);
|
||||
::glGenTextures(1, &texture.id);
|
||||
}
|
||||
auto const format{Array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8};
|
||||
bind_for_upload(texture);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
set_anisotropy(GL_TEXTURE_2D_ARRAY);
|
||||
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, Array.format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
texture.data_width = Array.size;
|
||||
texture.data_height = Array.size;
|
||||
texture.layers = Layers;
|
||||
texture.data_format = Array.format;
|
||||
texture.data_format = format;
|
||||
texture.data_components = GL_RGBA;
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::read_material(layer &Layer)
|
||||
void opengl46_terrain_materials::gpu_blank(slot const &Slot)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
Layer.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
|
||||
Layer.normalmap = (material.shader && material.shader->texture_conf.count("normalmap") > 0);
|
||||
Layer.specgloss = (material.shader && material.shader->texture_conf.count("specgloss") > 0);
|
||||
Layer.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
|
||||
}
|
||||
|
||||
int opengl46_terrain_materials::layer_index(gfx::terrain_layer const &Layer)
|
||||
{
|
||||
for (auto const &existing : m_layers)
|
||||
if (existing.material == Layer.material && existing.size == Layer.size)
|
||||
return existing.index;
|
||||
|
||||
layer entry;
|
||||
entry.material = Layer.material;
|
||||
entry.size = Layer.size;
|
||||
entry.index = static_cast<int>(m_layers.size());
|
||||
read_material(entry);
|
||||
m_layers.push_back(entry);
|
||||
m_pending = true;
|
||||
|
||||
if (static_cast<int>(m_layers.size()) > m_capacity)
|
||||
{
|
||||
// more room: the arrays are made anew, and every layer copied again
|
||||
auto const capacity{m_capacity * 2};
|
||||
for (auto &array : m_arrays)
|
||||
{
|
||||
auto &texture{m_textures.texture(array.handle)};
|
||||
::glDeleteTextures(1, &texture.id);
|
||||
::glGenTextures(1, &texture.id);
|
||||
make_storage(array, capacity);
|
||||
}
|
||||
m_capacity = capacity;
|
||||
for (auto &existing : m_layers)
|
||||
{
|
||||
existing.copied = false;
|
||||
existing.attempts = 0;
|
||||
}
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
return entry.index;
|
||||
}
|
||||
|
||||
std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse)
|
||||
{
|
||||
if (Reuse != null_handle)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Reuse)};
|
||||
if (lookup != m_slotof.end())
|
||||
{
|
||||
m_slots[lookup->second].used = true;
|
||||
return lookup->second;
|
||||
}
|
||||
}
|
||||
if (false == m_free.empty())
|
||||
{
|
||||
auto const index{m_free.back()};
|
||||
m_free.pop_back();
|
||||
m_slots[index].used = true;
|
||||
return index;
|
||||
}
|
||||
auto const index{m_slots.size()};
|
||||
auto const suffix{std::to_string(index)};
|
||||
slot entry;
|
||||
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, GL_TEXTURE_2D);
|
||||
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, GL_TEXTURE_2D);
|
||||
entry.table = make_texture("internal_src:terrain/table_" + suffix, GL_TEXTURE_2D);
|
||||
entry.holes = make_texture("internal_src:terrain/holes_" + suffix, GL_TEXTURE_2D);
|
||||
// the textures get their content before the material is made of them, which checks they're there
|
||||
std::uint8_t const blank[4]{0, 0, 0, 0};
|
||||
for (auto const handle : entry.weights)
|
||||
for (auto const handle : Slot.weights)
|
||||
{
|
||||
bind_for_upload(m_textures.texture(handle));
|
||||
bind_for_upload(m_gltextures.texture(handle));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
@@ -206,7 +101,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
{
|
||||
bind_for_upload(m_textures.texture(entry.table));
|
||||
bind_for_upload(m_gltextures.texture(Slot.table));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
std::array<float, max_layers * 2 * 4> const zeros{};
|
||||
@@ -214,7 +109,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
}
|
||||
{
|
||||
// led between the samples, so the edges of the holes run smoothly across the cells
|
||||
bind_for_upload(m_textures.texture(entry.holes));
|
||||
bind_for_upload(m_gltextures.texture(Slot.holes));
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
@@ -224,281 +119,89 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
opengl_texture::reset_unit_cache();
|
||||
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
|
||||
std::string const definition{
|
||||
"shader: terrain\n"
|
||||
"texture1: " + name(m_white) + "\n"
|
||||
"texture2: " + name(entry.weights[0]) + "\n"
|
||||
"texture3: " + name(entry.weights[1]) + "\n"
|
||||
"texture4: " + name(m_arrays[0].handle) + "\n"
|
||||
"texture5: " + name(m_arrays[1].handle) + "\n"
|
||||
"texture6: " + name(m_arrays[2].handle) + "\n"
|
||||
"texture7: " + name(entry.table) + "\n"
|
||||
"texture8: " + name(entry.holes) + "\n"};
|
||||
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
|
||||
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't make the material of a chunk");
|
||||
m_state = -1;
|
||||
return static_cast<std::size_t>(-1);
|
||||
}
|
||||
entry.used = true;
|
||||
m_slots.push_back(entry);
|
||||
m_slotof.emplace(entry.material, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
material_handle opengl46_terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples, std::uint8_t const *Holes)
|
||||
void opengl46_terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels)
|
||||
{
|
||||
if (Layers.empty() || false == init())
|
||||
return null_handle;
|
||||
auto const count{std::min(Layers.size(), max_layers)};
|
||||
std::array<int, max_layers> indices{};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (Layers[i].material == null_handle)
|
||||
return null_handle;
|
||||
indices[i] = layer_index(Layers[i]);
|
||||
}
|
||||
auto const slotindex{acquire_slot(Reuse)};
|
||||
if (slotindex == static_cast<std::size_t>(-1))
|
||||
return null_handle;
|
||||
auto &entry{m_slots[slotindex]};
|
||||
|
||||
// weights, four layers to a texture
|
||||
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
|
||||
auto const side{painted ? Samples + 1 : 1};
|
||||
auto const samples{static_cast<std::size_t>(side) * side};
|
||||
std::vector<std::uint8_t> texels(samples * 4);
|
||||
for (std::size_t texture = 0; texture < 2; ++texture)
|
||||
{
|
||||
std::fill(texels.begin(), texels.end(), 0);
|
||||
for (std::size_t channel = 0; channel < 4; ++channel)
|
||||
{
|
||||
auto const layer{texture * 4 + channel};
|
||||
if (layer >= count)
|
||||
break;
|
||||
if (false == painted)
|
||||
{
|
||||
if (layer == 0)
|
||||
texels[channel] = 255;
|
||||
continue;
|
||||
}
|
||||
auto const *source{Weights + layer * samples};
|
||||
for (std::size_t i = 0; i < samples; ++i)
|
||||
texels[i * 4 + channel] = source[i];
|
||||
}
|
||||
bind_for_upload(m_textures.texture(entry.weights[texture]));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
entry.count = count;
|
||||
entry.layers = indices;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
entry.rotations[i] = Layers[i].rotation;
|
||||
write_table(entry);
|
||||
|
||||
// the holes: the cells cut out entirely are left out of the mesh, the program of the holes cuts the rest of the openings
|
||||
auto const cut{Holes != nullptr && HoleSamples > 0};
|
||||
{
|
||||
auto const holeside{cut ? HoleSamples + 1 : 1};
|
||||
std::uint8_t const none{0};
|
||||
bind_for_upload(m_textures.texture(entry.holes));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, holeside, holeside, 0, GL_RED, GL_UNSIGNED_BYTE, cut ? Holes : &none);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
auto &material{m_materials.material(entry.material)};
|
||||
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
|
||||
if (m_holesprogram == nullptr && cut)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_holesprogram = GfxRenderer->Fetch_Shader("terrain_holes");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("heightmap terrain: can't use the shader of the holes: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
// the program of the holes discards fragments, which the depth prepass of the plain one never does
|
||||
material.shader = (cut && m_holesprogram != nullptr) ? m_holesprogram : m_program;
|
||||
return entry.material;
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Side, Side, 0, GL_RGBA, GL_UNSIGNED_BYTE, Texels);
|
||||
::glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::write_table(slot const &Slot)
|
||||
void opengl46_terrain_materials::gpu_table(texture_handle const Texture, float const *Table)
|
||||
{
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, Table);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples)
|
||||
{
|
||||
bind_for_upload(m_gltextures.texture(Texture));
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, Side, Side, 0, GL_RED, GL_UNSIGNED_BYTE, Samples);
|
||||
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_changed()
|
||||
{
|
||||
// what the layers are: index in the arrays, repeat, which maps they have, reflection
|
||||
std::array<float, max_layers * 2 * 4> table{};
|
||||
for (std::size_t i = 0; i < Slot.count; ++i)
|
||||
{
|
||||
auto const &layer{m_layers[Slot.layers[i]]};
|
||||
table[i * 4 + 0] = static_cast<float>(layer.index);
|
||||
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
|
||||
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
|
||||
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
|
||||
table[(max_layers + i) * 4 + 0] = layer.reflection;
|
||||
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
|
||||
auto const angle{glm::radians(static_cast<double>(Slot.rotations[i]))};
|
||||
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
|
||||
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
|
||||
}
|
||||
bind_for_upload(m_textures.texture(Slot.table));
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, table.data());
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::on_environment_change()
|
||||
void opengl46_terrain_materials::gpu_begin_copies()
|
||||
{
|
||||
if (m_state <= 0)
|
||||
return;
|
||||
bool changed{false};
|
||||
for (auto &layer : m_layers)
|
||||
{
|
||||
auto const &material{m_materials.material(layer.material)};
|
||||
if (false == (material.update_on_season_change || material.update_on_weather_change))
|
||||
continue;
|
||||
// the reloaded material can have other textures, other maps and another size
|
||||
read_material(layer);
|
||||
layer.copied = false;
|
||||
layer.attempts = 0;
|
||||
changed = true;
|
||||
}
|
||||
if (false == changed)
|
||||
return;
|
||||
m_pending = true;
|
||||
// the tables of the chunks hold what the layers have, freed slots get theirs anew when they're used again
|
||||
for (auto const &entry : m_slots)
|
||||
if (entry.used)
|
||||
write_table(entry);
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::release(material_handle const Material)
|
||||
{
|
||||
auto const lookup{m_slotof.find(Material)};
|
||||
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
|
||||
return;
|
||||
m_slots[lookup->second].used = false;
|
||||
m_free.push_back(lookup->second);
|
||||
}
|
||||
|
||||
bool opengl46_terrain_materials::copy_layer(layer &Layer)
|
||||
{
|
||||
auto const &material{m_materials.material(Layer.material)};
|
||||
auto const slot = [&](char const *Name, int const Fallback) {
|
||||
if (material.shader)
|
||||
{
|
||||
auto const lookup{material.shader->texture_conf.find(Name)};
|
||||
if (lookup != material.shader->texture_conf.end())
|
||||
return static_cast<int>(lookup->second.id);
|
||||
return -1;
|
||||
}
|
||||
return Fallback;
|
||||
};
|
||||
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
|
||||
// all maps of the layer have to be ready, so they go together
|
||||
std::array<opengl_texture *, 3> sources{};
|
||||
for (std::size_t i = 0; i < slots.size(); ++i)
|
||||
{
|
||||
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
|
||||
continue;
|
||||
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
|
||||
if (false == texture.is_ready)
|
||||
{
|
||||
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
|
||||
// one released by the sweep or never read has to be read first
|
||||
if (texture.data_state == resource_state::none)
|
||||
{
|
||||
texture.reload_on_use = false;
|
||||
texture.load();
|
||||
}
|
||||
if (texture.data_state == resource_state::failed)
|
||||
continue;
|
||||
if (false == texture.create())
|
||||
{
|
||||
if (++Layer.attempts < copy_attempts)
|
||||
return false;
|
||||
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
|
||||
continue; // waited long enough, the layer goes without it
|
||||
}
|
||||
}
|
||||
sources[i] = &texture;
|
||||
}
|
||||
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
|
||||
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
|
||||
for (std::size_t i = 0; i < m_arrays.size(); ++i)
|
||||
{
|
||||
auto const &array{m_arrays[i]};
|
||||
auto &target{m_textures.texture(array.handle)};
|
||||
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer.index);
|
||||
m_framebuffer->setup_drawing(1);
|
||||
::glViewport(0, 0, array.size, array.size);
|
||||
if (sources[i] == nullptr)
|
||||
{
|
||||
::glClearBufferfv(GL_COLOR, 0, defaults[i].data());
|
||||
continue;
|
||||
}
|
||||
if (array.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
m_copy->apply(*sources[i], m_framebuffer.get());
|
||||
}
|
||||
Layer.copied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::update()
|
||||
{
|
||||
if (false == m_pending || m_state <= 0)
|
||||
return;
|
||||
|
||||
auto const blend{::glIsEnabled(GL_BLEND)};
|
||||
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST)};
|
||||
auto const cull{::glIsEnabled(GL_CULL_FACE)};
|
||||
auto const depth{::glIsEnabled(GL_DEPTH_TEST)};
|
||||
auto const srgb{::glIsEnabled(GL_FRAMEBUFFER_SRGB)};
|
||||
m_saved.blend = ::glIsEnabled(GL_BLEND);
|
||||
m_saved.scissor = ::glIsEnabled(GL_SCISSOR_TEST);
|
||||
m_saved.cull = ::glIsEnabled(GL_CULL_FACE);
|
||||
m_saved.depth = ::glIsEnabled(GL_DEPTH_TEST);
|
||||
m_saved.srgb = ::glIsEnabled(GL_FRAMEBUFFER_SRGB);
|
||||
::glDisable(GL_BLEND);
|
||||
::glDisable(GL_SCISSOR_TEST);
|
||||
::glDisable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
bool copied{false};
|
||||
m_pending = false;
|
||||
for (auto &layer : m_layers)
|
||||
void opengl46_terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default)
|
||||
{
|
||||
auto &target{m_gltextures.texture(Array.handle)};
|
||||
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer);
|
||||
m_framebuffer->setup_drawing(1);
|
||||
::glViewport(0, 0, Array.size, Array.size);
|
||||
if (Source == nullptr)
|
||||
{
|
||||
if (layer.copied)
|
||||
continue;
|
||||
if (copy_layer(layer))
|
||||
copied = true;
|
||||
else
|
||||
m_pending = true;
|
||||
::glClearBufferfv(GL_COLOR, 0, Default.data());
|
||||
return;
|
||||
}
|
||||
if (copied)
|
||||
if (Array.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
m_copy->apply(static_cast<opengl_texture &>(*Source), m_framebuffer.get());
|
||||
}
|
||||
|
||||
void opengl46_terrain_materials::gpu_end_copies(bool const Copied)
|
||||
{
|
||||
if (Copied)
|
||||
{
|
||||
for (auto const &array : m_arrays)
|
||||
for (auto const &array : arrays())
|
||||
{
|
||||
bind_for_upload(m_textures.texture(array.handle));
|
||||
bind_for_upload(m_gltextures.texture(array.handle));
|
||||
::glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
// leave the state as the rest of the renderer knows it
|
||||
gl::framebuffer::unbind();
|
||||
if (blend)
|
||||
if (m_saved.blend)
|
||||
::glEnable(GL_BLEND);
|
||||
if (scissor)
|
||||
if (m_saved.scissor)
|
||||
::glEnable(GL_SCISSOR_TEST);
|
||||
if (cull)
|
||||
if (m_saved.cull)
|
||||
::glEnable(GL_CULL_FACE);
|
||||
if (depth)
|
||||
if (m_saved.depth)
|
||||
::glEnable(GL_DEPTH_TEST);
|
||||
else
|
||||
::glDisable(GL_DEPTH_TEST);
|
||||
if (srgb)
|
||||
if (m_saved.srgb)
|
||||
::glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
::glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
|
||||
@@ -9,91 +9,44 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "model/material.h"
|
||||
#include "model/Texture.h"
|
||||
#include "rendering/common/terrainmaterials.h"
|
||||
#include "gl/framebuffer.h"
|
||||
#include "gl/postfx.h"
|
||||
|
||||
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes).
|
||||
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
|
||||
// of its own, with textures holding the weights of its layers and a table of what its layers are
|
||||
class opengl46_terrain_materials
|
||||
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes):
|
||||
// the textures of gfx::terrain_materials as opengl textures, the layer maps copied into the arrays by a draw of the copy shader
|
||||
class opengl46_terrain_materials : public gfx::terrain_materials
|
||||
{
|
||||
public:
|
||||
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : m_materials(Materials), m_textures(Textures) {}
|
||||
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : gfx::terrain_materials(Materials, Textures), m_gltextures(Textures) {}
|
||||
|
||||
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
|
||||
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
|
||||
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr);
|
||||
void release(material_handle const Material);
|
||||
// copies the textures of new layers into the arrays; to be called before the frame is drawn
|
||||
void update();
|
||||
// the layer materials which change with the season or the weather were reloaded, their textures are copied again
|
||||
void on_environment_change();
|
||||
protected:
|
||||
bool gpu_init() override;
|
||||
void gpu_texture(texture_source &Texture, texture_kind const Kind) override;
|
||||
void gpu_white(texture_source &Texture) override;
|
||||
void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) override;
|
||||
void gpu_blank(slot const &Slot) override;
|
||||
void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) override;
|
||||
void gpu_table(texture_handle const Texture, float const *Table) override;
|
||||
void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) override;
|
||||
void gpu_begin_copies() override;
|
||||
void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) override;
|
||||
void gpu_end_copies(bool const Copied) override;
|
||||
void gpu_changed() override;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t max_layers{8};
|
||||
|
||||
struct layer
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
float size{0.f}; // as given by the chunk
|
||||
float repeat{8.f}; // metres the textures are repeated at
|
||||
int index{0}; // in the arrays
|
||||
bool normalmap{false};
|
||||
bool specgloss{false};
|
||||
float reflection{0.f};
|
||||
bool copied{false};
|
||||
int attempts{0};
|
||||
};
|
||||
struct texture_array
|
||||
{
|
||||
texture_handle handle{null_handle};
|
||||
int size{0};
|
||||
GLint format{0};
|
||||
bool srgb{false};
|
||||
};
|
||||
struct slot
|
||||
{
|
||||
material_handle material{null_handle};
|
||||
std::array<texture_handle, 2> weights{null_handle, null_handle};
|
||||
texture_handle table{null_handle};
|
||||
texture_handle holes{null_handle}; // where the ground is cut, R8; a single texel of 0 without holes
|
||||
bool used{false};
|
||||
// what the table was made of, kept to make it again when a layer changes
|
||||
std::size_t count{0};
|
||||
std::array<int, max_layers> layers{};
|
||||
std::array<float, max_layers> rotations{};
|
||||
};
|
||||
|
||||
bool init();
|
||||
texture_handle make_texture(std::string const &Name, GLenum const Target);
|
||||
void make_storage(texture_array &Array, int const Layers);
|
||||
void read_material(layer &Layer);
|
||||
int layer_index(gfx::terrain_layer const &Layer);
|
||||
void write_table(slot const &Slot);
|
||||
bool copy_layer(layer &Layer);
|
||||
std::size_t acquire_slot(material_handle const Reuse);
|
||||
|
||||
material_manager &m_materials;
|
||||
texture_manager &m_textures;
|
||||
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
|
||||
texture_handle m_white{null_handle};
|
||||
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
|
||||
int m_capacity{0}; // layers the arrays have room for
|
||||
std::vector<layer> m_layers;
|
||||
bool m_pending{false};
|
||||
std::vector<slot> m_slots;
|
||||
std::vector<std::size_t> m_free;
|
||||
std::unordered_map<material_handle, std::size_t> m_slotof;
|
||||
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
|
||||
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
|
||||
texture_manager &m_gltextures;
|
||||
std::unique_ptr<gl::postfx> m_copy;
|
||||
std::unique_ptr<gl::framebuffer> m_framebuffer;
|
||||
// the state of opengl the copies change, given back after them
|
||||
struct
|
||||
{
|
||||
GLboolean blend{GL_FALSE};
|
||||
GLboolean scissor{GL_FALSE};
|
||||
GLboolean cull{GL_FALSE};
|
||||
GLboolean depth{GL_FALSE};
|
||||
GLboolean srgb{GL_FALSE};
|
||||
} m_saved;
|
||||
};
|
||||
|
||||
@@ -11,14 +11,11 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46weather.h"
|
||||
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
#include "environment/starcatalog.h"
|
||||
#include "environment/weather.h"
|
||||
#include "simulation/simulationenvironment.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "widgets/weatherpanel.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -27,47 +24,6 @@ namespace
|
||||
int const SHAPE_NOISE_SIZE{128};
|
||||
int const DETAIL_NOISE_SIZE{32};
|
||||
int const COVERAGE_NOISE_SIZE{512};
|
||||
// how many times the cell of a genus fits in the tile of the shape noise, and its spread in the tile of the coverage noise
|
||||
double const SHAPE_TILE_CELLS{4.0};
|
||||
double const COVERAGE_TILE_SPREADS{4.0};
|
||||
// size of the patches of the ground fog
|
||||
float const FOG_PATCH_SIZE{300.f};
|
||||
// the haze the atmosphere draws in the sky already, as an extinction: about the haze of a day with 30 km of visibility
|
||||
float const SKY_HAZE_EXTINCTION{3.f / 30000.f};
|
||||
// near end of the fog volume (view depth, metres)
|
||||
float const FOG_VOLUME_NEAR{0.5f};
|
||||
// bands the panorama is drawn in, one a frame
|
||||
int const PANORAMA_BANDS{8};
|
||||
// brightness of a star of magnitude 0 in the units of the scene
|
||||
float const STAR_BRIGHTNESS{4.f};
|
||||
// stars dimmer than this aren't drawn
|
||||
float const STAR_LIMIT{6.5f};
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
// the position in a tiling noise, of a period in metres, kept small for the single precision of the shaders
|
||||
double wrap(double const Value, double const Period)
|
||||
{
|
||||
return Value - Period * std::floor(Value / Period);
|
||||
}
|
||||
|
||||
// the colour of a star of a colour temperature, linear, of luminance 1, somewhat paler than the black body: the eye sees most
|
||||
// stars white
|
||||
glm::vec3 star_color(float const Temperature)
|
||||
{
|
||||
auto const t{std::clamp(Temperature, 1500.f, 40000.f) / 100.f};
|
||||
glm::vec3 color;
|
||||
color.r = (t <= 66.f) ? 255.f : 329.698727446f * std::pow(t - 60.f, -0.1332047592f);
|
||||
color.g = (t <= 66.f) ? 99.4708025861f * std::log(t) - 161.1195681661f : 288.1221695283f * std::pow(t - 60.f, -0.0755148492f);
|
||||
color.b = (t >= 66.f) ? 255.f : (t <= 19.f) ? 0.f : 138.5177312231f * std::log(t - 10.f) - 305.0447927307f;
|
||||
color = glm::pow(glm::clamp(color / 255.f, glm::vec3{0.f}, glm::vec3{1.f}), glm::vec3{2.2f});
|
||||
color /= std::max(luminance(color), 1e-3f);
|
||||
return glm::mix(color, glm::vec3{1.f}, 0.5f);
|
||||
}
|
||||
|
||||
std::unique_ptr<gl::program> make_compute(std::string const &Name)
|
||||
{
|
||||
gl::shader shader(Name);
|
||||
@@ -118,26 +74,6 @@ GLuint opengl46_weather::create_texture(GLenum const Target, GLenum const Format
|
||||
return texture;
|
||||
}
|
||||
|
||||
opengl46_weather::quality_level opengl46_weather::quality(int const Clouds, int const Fog)
|
||||
{
|
||||
quality_level level;
|
||||
switch (std::clamp(Clouds, 1, 4))
|
||||
{
|
||||
case 1: level = {4, 32, 3, false, {512, 128}, {}}; break;
|
||||
case 2: level = {2, 48, 4, true, {1024, 256}, {}}; break;
|
||||
case 3: level = {2, 80, 6, true, {1024, 256}, {}}; break;
|
||||
default: level = {2, 128, 8, true, {2048, 512}, {}}; break;
|
||||
}
|
||||
switch (std::clamp(Fog, 1, 4))
|
||||
{
|
||||
case 1: level.fog_grid = {80, 45, 48}; break;
|
||||
case 2: level.fog_grid = {160, 90, 64}; break;
|
||||
case 3: level.fog_grid = {160, 90, 96}; break;
|
||||
default: level.fog_grid = {240, 135, 128}; break;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
void opengl46_weather::init()
|
||||
{
|
||||
m_noise_program = make_compute("clouds_noise.comp");
|
||||
@@ -150,32 +86,12 @@ void opengl46_weather::init()
|
||||
m_sky_program = make_program("quad.vert", "sky_weather.frag");
|
||||
m_stars_program = make_program("stars.vert", "stars.frag");
|
||||
|
||||
// a grey fog and no clouds until the first update
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{0.5f, 0.5f, 0.5f, 1.f};
|
||||
m_weather.fog_haze = glm::vec4{3.f / 10000.f, 1.f / 800.f, SKY_HAZE_EXTINCTION, 1.f};
|
||||
m_weather.fog_light = glm::vec4{0.f, 1.f, 0.6f, 1.f / 5000.f};
|
||||
m_weather_ubo = std::make_unique<gl::ubo>(sizeof(gl::weather_ubs), gl::WEATHER_UBO_BINDING);
|
||||
m_weather_ubo->update(m_weather);
|
||||
m_weather_ubo->update(m_state.block());
|
||||
|
||||
make_noise();
|
||||
|
||||
// the stars, in the equatorial frame
|
||||
std::vector<glm::vec4> stars;
|
||||
auto const *catalogue{star_catalogue::stars()};
|
||||
for (std::size_t i = 0; i < star_catalogue::size(); ++i)
|
||||
{
|
||||
auto const &star{catalogue[i]};
|
||||
auto const magnitude{static_cast<float>(star.magnitude) * 0.01f};
|
||||
if (magnitude > STAR_LIMIT)
|
||||
continue;
|
||||
auto const ascension{glm::radians(static_cast<float>(star.right_ascension) * 0.01f)};
|
||||
auto const declination{glm::radians(static_cast<float>(star.declination) * 0.01f)};
|
||||
stars.emplace_back(std::cos(declination) * std::cos(ascension), std::cos(declination) * std::sin(ascension), std::sin(declination), magnitude);
|
||||
// a phase of the twinkling which doesn't repeat from star to star
|
||||
auto const phase{std::fmod(static_cast<float>(i) * 0.6180339887f, 1.f)};
|
||||
stars.emplace_back(star_color(static_cast<float>(star.temperature) * 10.f), phase);
|
||||
}
|
||||
auto const stars{gfx::weather_state::star_vertices()};
|
||||
m_stars_count = static_cast<int>(stars.size() / 2);
|
||||
m_stars_vao.emplace();
|
||||
m_stars_vao->bind();
|
||||
@@ -249,105 +165,15 @@ void opengl46_weather::bind_noise() const
|
||||
void opengl46_weather::upload_header()
|
||||
{
|
||||
// the colours of the horizon are left alone when the gpu writes them
|
||||
auto const size{m_horizon_on_gpu ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
|
||||
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_weather), 0, static_cast<GLsizeiptr>(size));
|
||||
auto const size{m_state.horizon_on_gpu() ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
|
||||
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_state.block()), 0, static_cast<GLsizeiptr>(size));
|
||||
m_weather_ubo->bind_uniform();
|
||||
}
|
||||
|
||||
void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera)
|
||||
{
|
||||
auto const &weather{simulation::Environment.weather()};
|
||||
auto const &conditions{weather.conditions()};
|
||||
m_fog_volume_valid = false;
|
||||
m_clouds_view_valid = false;
|
||||
++m_frame;
|
||||
|
||||
auto const height{static_cast<float>(Camera.y) - weather.ground()};
|
||||
auto const time{weather.time()};
|
||||
auto const sun{glm::normalize(Keylight)};
|
||||
|
||||
// the fog. the visibility is where a dark object fades to 5%: the extinction is 3 over it
|
||||
auto const haze{3.f / conditions.visibility};
|
||||
auto const hazerate{1.f / conditions.haze_height};
|
||||
m_weather.fog_haze = glm::vec4{haze, hazerate, SKY_HAZE_EXTINCTION, 1.f};
|
||||
if (conditions.groundfog_thickness > 0.f)
|
||||
{
|
||||
auto const thickness{conditions.groundfog_thickness};
|
||||
m_weather.fog_ground = glm::vec4{std::max(3.f / conditions.groundfog_visibility - haze, 0.f), 1.f / (0.25f * thickness), 0.75f * thickness, conditions.fog_noise};
|
||||
}
|
||||
else
|
||||
m_weather.fog_ground = glm::vec4{0.f, 1.f, 0.f, conditions.fog_noise};
|
||||
// the patches drift with the wind, slower than the clouds
|
||||
auto const drift{glm::dvec2{weather.cloud_offset()} * 0.3};
|
||||
auto const patch{static_cast<double>(FOG_PATCH_SIZE)};
|
||||
m_weather.fog_noise = glm::vec4{static_cast<float>(wrap(Camera.x - drift.x, patch)), static_cast<float>(wrap(Camera.y, patch)), static_cast<float>(wrap(Camera.z - drift.y, patch)), 1.f / FOG_PATCH_SIZE};
|
||||
// the extinction at the height of the camera tells how far things are hidden
|
||||
auto const extinction{haze * std::exp(-hazerate * std::max(height, 0.f)) +
|
||||
m_weather.fog_ground.x * std::exp(-m_weather.fog_ground.y * std::max(height - m_weather.fog_ground.z, 0.f))};
|
||||
// 2% left at 1.3 visibilities; the draw range is twice this
|
||||
m_fog_range = std::clamp(3.f / std::max(extinction, 1e-7f) / 1.5f, 5.f, 1e6f);
|
||||
|
||||
// the share of the light of the fog the sun gives, which the shadows hold back
|
||||
auto const sunlight{luminance(Keycolor) * 1.5f * std::max(sun.y, 0.f)};
|
||||
auto const skylight{luminance(Ambient) * 0.65f};
|
||||
m_sun_share = (sunlight + skylight > 1e-6f) ? sunlight / (sunlight + skylight) : 0.f;
|
||||
|
||||
// clouds
|
||||
auto const layers{std::min(conditions.clouds.size(), gl::MAX_CLOUD_LAYERS)};
|
||||
m_clouds_drawn = (Global.gfx_clouds > 0) && (layers > 0);
|
||||
m_average_transmittance = 1.f;
|
||||
auto const velocity{weather.cloud_velocity()};
|
||||
auto const speed{glm::length(velocity)};
|
||||
auto const wind{speed > 1e-3f ? glm::dvec2{velocity / speed} : glm::dvec2{1.0, 0.0}};
|
||||
m_weather.cloud_wind = glm::vec4{static_cast<float>(wind.x), static_cast<float>(wind.y), speed, m_cloud_detail_scale};
|
||||
for (std::size_t i = 0; i < gl::MAX_CLOUD_LAYERS; ++i)
|
||||
{
|
||||
auto &target{m_weather.cloud_layers[i]};
|
||||
if (i >= layers)
|
||||
{
|
||||
target = gl::cloud_layer_ubs{};
|
||||
continue;
|
||||
}
|
||||
auto const &layer{conditions.clouds[i]};
|
||||
auto const &genus{cloud_genus_info(layer.type)};
|
||||
target.heights = glm::vec4{layer.base, layer.top, 1.f / std::max(layer.top - layer.base, 1.f), static_cast<float>(layer.type)};
|
||||
auto const shapetile{static_cast<double>(genus.cell) * SHAPE_TILE_CELLS};
|
||||
auto const coveragetile{static_cast<double>(genus.spread) * COVERAGE_TILE_SPREADS};
|
||||
target.shape = glm::vec4{layer.coverage, genus.extinction * layer.density, static_cast<float>(1.0 / shapetile), genus.erosion};
|
||||
target.profile = glm::vec4{genus.bottom, genus.top, genus.top_variation, genus.anvil};
|
||||
// the higher, the faster the wind carries them
|
||||
auto const offset{glm::dvec2{weather.cloud_offset()} * (1.0 + static_cast<double>(layer.base) / 8000.0)};
|
||||
// the camera's place in the noise, in the space stretched along the wind as the shaders take it
|
||||
auto const place{glm::dvec2{Camera.x, Camera.z} - offset};
|
||||
auto const along{glm::dot(place, wind)};
|
||||
auto const stretched{(place - wind * along) + wind * (along / std::max(static_cast<double>(genus.stretch), 1.0))};
|
||||
target.motion = glm::vec4{static_cast<float>(wrap(stretched.x, shapetile)), static_cast<float>(wrap(stretched.y, shapetile)), static_cast<float>(1.0 / coveragetile), genus.stretch};
|
||||
// the clouds change their shapes slowly; the seed moves the layers apart in the noise
|
||||
auto const evolution{std::fmod(static_cast<double>(conditions.seed) * 0.6180339887 + static_cast<double>(i) * 0.37 + time * 0.00002, 1.0)};
|
||||
target.extra = glm::vec4{genus.flat ? 1.f : 0.f, static_cast<float>(evolution), static_cast<float>(wrap(stretched.x, coveragetile)), static_cast<float>(wrap(stretched.y, coveragetile))};
|
||||
m_average_transmittance *= 1.f - std::clamp(layer.coverage * genus.opacity * std::min(layer.density, 1.5f), 0.f, 1.f);
|
||||
}
|
||||
if (Global.gfx_clouds <= 0)
|
||||
m_average_transmittance = 1.f;
|
||||
m_weather.fog_light = glm::vec4{m_sun_share, m_average_transmittance, 0.6f, fog_density()};
|
||||
m_weather.weather_camera = glm::vec4{height, static_cast<float>(std::fmod(time, 100000.0)), m_clouds_drawn ? 1.f : 0.f, m_clouds_drawn ? static_cast<float>(layers) : 0.f};
|
||||
m_weather.weather_sun_direction = glm::vec4{sun, sun.y > -0.02f ? 1.f : 0.f};
|
||||
m_weather.weather_sun_color = glm::vec4{Keycolor, m_cloud_sun_scale};
|
||||
m_weather.weather_ambient = glm::vec4{Ambient * 1.3f, m_cloud_sky_scale};
|
||||
m_weather.fog_volume_params = glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target = glm::vec4{1.f, 1.f, Global.gfx_fog_range, 1.f - Sky.atmosphere_weight()};
|
||||
|
||||
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
|
||||
m_horizon_on_gpu = (Sky.atmosphere_weight() > 0.f) && (Sky.skyview_table() != 0);
|
||||
if (false == m_horizon_on_gpu)
|
||||
{
|
||||
for (auto &color : m_weather.fog_horizon)
|
||||
color = glm::vec4{Fogcolor, 1.f};
|
||||
}
|
||||
|
||||
// the shadows of the clouds, on a map snapped to its texels so it doesn't crawl as the camera moves
|
||||
auto const shadows{m_clouds_drawn && Global.gfx_clouds_shadows && (sun.y > 0.02f)};
|
||||
m_weather.cloud_shadow_params = glm::vec4{0.f};
|
||||
m_state.update(Sky.atmosphere_weight(), Sky.skyview_table() != 0, Keylight, Keycolor, Ambient, Fogcolor, Camera);
|
||||
auto const shadows{m_state.shadows_wanted()};
|
||||
if (shadows)
|
||||
{
|
||||
if (m_shadow_size != Global.gfx_clouds_shadows_size)
|
||||
@@ -356,14 +182,11 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
m_shadow_size = Global.gfx_clouds_shadows_size;
|
||||
m_shadow_map = create_texture(GL_TEXTURE_2D, GL_R16F, glm::ivec3{m_shadow_size, m_shadow_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
auto const extent{static_cast<double>(Global.gfx_clouds_shadows_range)};
|
||||
auto const texel{extent / static_cast<double>(m_shadow_size)};
|
||||
auto const centre{glm::dvec2{std::round(Camera.x / texel) * texel - Camera.x, std::round(Camera.z / texel) * texel - Camera.z}};
|
||||
m_weather.cloud_shadow_params = glm::vec4{static_cast<float>(centre.x), static_cast<float>(centre.y), static_cast<float>(1.0 / extent), 0.f};
|
||||
m_state.place_shadows(m_shadow_size, Camera);
|
||||
}
|
||||
upload_header();
|
||||
|
||||
if (m_horizon_on_gpu)
|
||||
if (m_state.horizon_on_gpu())
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, Sky.skyview_table());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, gl::WEATHER_SCRATCH_SSBO_BINDING, *m_weather_ubo);
|
||||
@@ -383,35 +206,32 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
bind_noise();
|
||||
glBindImageTexture(0, m_shadow_map, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
|
||||
glProgramUniform1i(*m_shadow_program, 1, m_shadow_size);
|
||||
glProgramUniform3f(*m_shadow_program, 2, m_weather.cloud_shadow_params.x, m_weather.cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
|
||||
glProgramUniform3f(*m_shadow_program, 2, m_state.block().cloud_shadow_params.x, m_state.block().cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
|
||||
glProgramUniform1i(*m_shadow_program, 3, 8);
|
||||
m_shadow_program->dispatch(groups(m_shadow_size), groups(m_shadow_size), 1);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
|
||||
m_shadow_timer.end();
|
||||
m_shadows_valid = true;
|
||||
m_weather.cloud_shadow_params.w = 1.f;
|
||||
m_state.shadows_drawn(true);
|
||||
upload_header();
|
||||
}
|
||||
else
|
||||
m_shadows_valid = false;
|
||||
m_state.shadows_drawn(false);
|
||||
|
||||
// a band of the panorama of the clouds, all of it the first time
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
if (m_panorama_size != level.panorama)
|
||||
{
|
||||
release(m_panorama);
|
||||
m_panorama_size = level.panorama;
|
||||
m_panorama = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_panorama_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(m_panorama, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
m_panorama_full = false;
|
||||
m_panorama_band = 0;
|
||||
m_state.panorama_resized();
|
||||
}
|
||||
auto const bandrows{(m_panorama_size.y + PANORAMA_BANDS - 1) / PANORAMA_BANDS};
|
||||
auto const first{m_panorama_full ? m_panorama_band * bandrows : 0};
|
||||
auto const rows{m_panorama_full ? bandrows : m_panorama_size.y};
|
||||
int first, rows;
|
||||
m_state.panorama_band(m_panorama_size, first, rows);
|
||||
m_panorama_timer.begin();
|
||||
bind_noise();
|
||||
glBindImageTexture(0, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -419,7 +239,7 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
glBindImageTexture(1, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glProgramUniform1i(*m_march_program, 0, 1);
|
||||
glProgramUniform2i(*m_march_program, 1, m_panorama_size.x, m_panorama_size.y);
|
||||
glProgramUniform1i(*m_march_program, 4, m_frame);
|
||||
glProgramUniform1i(*m_march_program, 4, m_state.frame());
|
||||
// the panorama is seen blurred: fewer samples
|
||||
glProgramUniform4f(*m_march_program, 5, static_cast<float>(std::max(level.steps / 2, 24)), static_cast<float>(std::max(level.light_steps - 1, 2)), Global.gfx_clouds_distance, 0.f);
|
||||
glProgramUniform2i(*m_march_program, 6, first, rows);
|
||||
@@ -428,17 +248,14 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glBindImageTexture(1, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_panorama_timer.end();
|
||||
m_panorama_band = m_panorama_full ? (m_panorama_band + 1) % PANORAMA_BANDS : 0;
|
||||
m_panorama_full = true;
|
||||
}
|
||||
gl::program::unbind();
|
||||
m_report = to_metar(conditions);
|
||||
}
|
||||
|
||||
void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const &Level)
|
||||
{
|
||||
auto const cloudsize{glm::max((Size + Level.divisor - 1) / Level.divisor, glm::ivec2{1})};
|
||||
if (m_clouds_drawn && (cloudsize != m_view_size || m_clouds_color == 0))
|
||||
if (m_state.clouds_drawn() && (cloudsize != m_view_size || m_clouds_color == 0))
|
||||
{
|
||||
for (auto *texture : {&m_clouds_color, &m_clouds_depth, &m_clouds_history[0], &m_clouds_history[1]})
|
||||
release(*texture);
|
||||
@@ -447,7 +264,7 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
|
||||
m_clouds_depth = create_texture(GL_TEXTURE_2D, GL_R32F, glm::ivec3{m_view_size, 1}, GL_NEAREST, GL_CLAMP_TO_EDGE);
|
||||
for (auto &history : m_clouds_history)
|
||||
history = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_view_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
m_previous_clouds.valid = false;
|
||||
m_state.previous_clouds.valid = false;
|
||||
}
|
||||
if ((Global.gfx_fog == 1) && (Level.fog_grid != m_fog_grid || m_fog_integrated == 0))
|
||||
{
|
||||
@@ -458,23 +275,23 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
|
||||
scattering = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
// one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp)
|
||||
m_fog_integrated = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid + glm::ivec3{0, 0, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
||||
m_previous_fog.valid = false;
|
||||
m_state.previous_fog.valid = false;
|
||||
}
|
||||
m_view_level = Level;
|
||||
}
|
||||
|
||||
void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp)
|
||||
{
|
||||
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
|
||||
prepare_view(Size, level);
|
||||
auto const rotation{glm::mat4{glm::mat3{View}}};
|
||||
auto const inverseprojection{glm::inverse(Projection)};
|
||||
auto const viewtoworld{glm::transpose(rotation)};
|
||||
auto const projectionview{Projection * rotation};
|
||||
// a jump of the camera (another vehicle, the free camera) leaves nothing to blend with
|
||||
auto const continuity = [&](view_state const &Previous) { return Previous.valid && glm::length(Previous.camera - Camera) < 50.0; };
|
||||
auto &previousclouds{m_state.previous_clouds};
|
||||
auto &previousfog{m_state.previous_fog};
|
||||
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
m_clouds_timer.begin();
|
||||
bind_noise();
|
||||
@@ -484,7 +301,7 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniform2i(*m_march_program, 1, m_view_size.x, m_view_size.y);
|
||||
glProgramUniformMatrix4fv(*m_march_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_march_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniform1i(*m_march_program, 4, m_frame);
|
||||
glProgramUniform1i(*m_march_program, 4, m_state.frame());
|
||||
glProgramUniform4f(*m_march_program, 5, static_cast<float>(level.steps), static_cast<float>(level.light_steps), Global.gfx_clouds_distance, level.detail ? 1.f : 0.f);
|
||||
glProgramUniform2i(*m_march_program, 6, 0, m_view_size.y);
|
||||
m_march_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
|
||||
@@ -499,10 +316,10 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniform2i(*m_temporal_program, 1, m_view_size.x, m_view_size.y);
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_clouds.projection_view));
|
||||
auto const previous{glm::vec3{m_previous_clouds.camera - Camera}};
|
||||
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(previousclouds.projection_view));
|
||||
auto const previous{glm::vec3{previousclouds.camera - Camera}};
|
||||
glProgramUniform3f(*m_temporal_program, 5, previous.x, previous.y, previous.z);
|
||||
glProgramUniform1f(*m_temporal_program, 6, continuity(m_previous_clouds) ? 0.15f : 1.f);
|
||||
glProgramUniform1f(*m_temporal_program, 6, gfx::weather_state::continuity(previousclouds, Camera) ? 0.15f : 1.f);
|
||||
m_temporal_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -510,31 +327,18 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glBindTextureUnit(gl::CLOUD_SHAPE_TEX, m_shape_noise);
|
||||
m_clouds_current = next;
|
||||
m_clouds_timer.end();
|
||||
m_previous_clouds = {projectionview, Camera, {}, true};
|
||||
m_clouds_view_valid = true;
|
||||
previousclouds = {projectionview, Camera, {}, true};
|
||||
m_state.clouds_made();
|
||||
}
|
||||
else
|
||||
m_previous_clouds.valid = false;
|
||||
previousclouds.valid = false;
|
||||
|
||||
if (Global.gfx_fog == 1)
|
||||
{
|
||||
m_fog_timer.begin();
|
||||
auto const farthest{std::max(Global.gfx_fog_range, FOG_VOLUME_NEAR * 4.f)};
|
||||
// the cells are sampled at another point in every frame: along the depth in the Halton sequence of 2, across the view in
|
||||
// those of 3 and 5 (fog_inject.comp moves the point of every cell by a value of its own)
|
||||
auto const halton = [](int Index, int const Base) {
|
||||
float value{0.f}, fraction{1.f};
|
||||
for (; Index > 0; Index /= Base)
|
||||
{
|
||||
fraction /= static_cast<float>(Base);
|
||||
value += fraction * static_cast<float>(Index % Base);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
auto const temporal{Global.gfx_fog_temporal && continuity(m_previous_fog)};
|
||||
auto const sample{(m_frame % 64) + 1};
|
||||
glm::vec3 const jitter{temporal ? glm::vec3{halton(sample, 3), halton(sample, 5), halton(sample, 2)} : glm::vec3{0.5f}};
|
||||
glm::vec4 const slicing{FOG_VOLUME_NEAR, farthest, static_cast<float>(m_fog_grid.z) / std::log((farthest + gl::FOG_VOLUME_BIAS) / (FOG_VOLUME_NEAR + gl::FOG_VOLUME_BIAS)), jitter.z};
|
||||
auto const temporal{Global.gfx_fog_temporal && gfx::weather_state::continuity(previousfog, Camera)};
|
||||
auto const jitter{m_state.fog_jitter(temporal)};
|
||||
auto const slicing{gfx::weather_state::fog_slicing(m_fog_grid.z, jitter)};
|
||||
bind_noise();
|
||||
auto const next{1 - m_fog_current};
|
||||
glBindImageTexture(0, m_fog_scattering[next], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
@@ -543,14 +347,14 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 1, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 2, 1, GL_FALSE, glm::value_ptr(viewtoworld));
|
||||
glProgramUniform4fv(*m_inject_program, 3, 1, glm::value_ptr(slicing));
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_fog.projection_view));
|
||||
auto const previous{glm::vec3{m_previous_fog.camera - Camera}};
|
||||
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(previousfog.projection_view));
|
||||
auto const previous{glm::vec3{previousfog.camera - Camera}};
|
||||
glProgramUniform3f(*m_inject_program, 5, previous.x, previous.y, previous.z);
|
||||
auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled};
|
||||
auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f};
|
||||
glProgramUniform4f(*m_inject_program, 6, share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f);
|
||||
glProgramUniform1i(*m_inject_program, 7, Cablamp);
|
||||
glProgramUniform2f(*m_inject_program, 8, m_previous_fog.slicing.x, m_previous_fog.slicing.z);
|
||||
glProgramUniform2f(*m_inject_program, 8, previousfog.slicing.x, previousfog.slicing.z);
|
||||
glProgramUniform2f(*m_inject_program, 9, jitter.x, jitter.y);
|
||||
m_inject_program->dispatch(groups(m_fog_grid.x), groups(m_fog_grid.y), static_cast<GLuint>(m_fog_grid.z));
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
@@ -565,50 +369,34 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_fog_current = next;
|
||||
m_fog_slicing = slicing;
|
||||
m_fog_timer.end();
|
||||
m_previous_fog = {projectionview, Camera, slicing, true};
|
||||
m_fog_volume_valid = true;
|
||||
previousfog = {projectionview, Camera, slicing, true};
|
||||
m_state.fog_made(slicing, m_fog_grid);
|
||||
}
|
||||
else
|
||||
m_previous_fog.valid = false;
|
||||
previousfog.valid = false;
|
||||
gl::program::unbind();
|
||||
}
|
||||
|
||||
void opengl46_weather::setup_pass(bool const Volume, glm::ivec2 const &Size)
|
||||
{
|
||||
auto const volume{Volume && m_fog_volume_valid && (Global.gfx_fog == 1)};
|
||||
m_weather.fog_volume_params = volume ? glm::vec4{1.f, m_fog_slicing.x, m_fog_slicing.z, static_cast<float>(m_fog_grid.z)} : glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
|
||||
m_weather.fog_volume_target.x = 1.f / static_cast<float>(std::max(Size.x, 1));
|
||||
m_weather.fog_volume_target.y = 1.f / static_cast<float>(std::max(Size.y, 1));
|
||||
m_weather.fog_volume_target.z = volume ? m_fog_slicing.y : Global.gfx_fog_range;
|
||||
m_state.setup_pass(Volume, Size);
|
||||
upload_header();
|
||||
bind();
|
||||
}
|
||||
|
||||
void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, double const Siderealtime, double const Latitude)
|
||||
{
|
||||
if (!Global.gfx_stars || !m_stars_vao || m_stars_count == 0)
|
||||
if (!m_stars_vao || m_stars_count == 0)
|
||||
return;
|
||||
// the stars come out between the civil and the nautical twilight
|
||||
auto const night{1.f - glm::smoothstep(-12.f, -3.f, Sunangle)};
|
||||
if (night <= 0.f)
|
||||
glm::mat4 toworld, toview;
|
||||
glm::vec4 params;
|
||||
if (false == m_state.stars(Sunangle, Siderealtime, Latitude, View, toworld, toview, params))
|
||||
return;
|
||||
// the equatorial frame (z to the pole, x to the vernal equinox) to the sky of the observer (x west, y up, z north)
|
||||
auto const theta{glm::radians(Siderealtime)};
|
||||
auto const phi{glm::radians(Latitude)};
|
||||
auto const st{static_cast<float>(std::sin(theta))}, ct{static_cast<float>(std::cos(theta))};
|
||||
auto const sp{static_cast<float>(std::sin(phi))}, cp{static_cast<float>(std::cos(phi))};
|
||||
glm::mat3 equatorial;
|
||||
equatorial[0] = glm::vec3{st, cp * ct, -sp * ct};
|
||||
equatorial[1] = glm::vec3{-ct, cp * st, -sp * st};
|
||||
equatorial[2] = glm::vec3{0.f, sp, cp};
|
||||
auto const toworld{glm::mat4{equatorial}};
|
||||
auto const toview{glm::mat4{glm::mat3{View}}};
|
||||
m_stars_program->bind();
|
||||
glProgramUniformMatrix4fv(*m_stars_program, 0, 1, GL_FALSE, glm::value_ptr(toworld));
|
||||
glProgramUniformMatrix4fv(*m_stars_program, 1, 1, GL_FALSE, glm::value_ptr(toview));
|
||||
glProgramUniform4f(*m_stars_program, 2, STAR_BRIGHTNESS * Global.gfx_stars_brightness * night, static_cast<float>(std::fmod(Timer::GetTime(), 10000.0)), 2.5f, 0.f);
|
||||
glProgramUniform4fv(*m_stars_program, 2, 1, glm::value_ptr(params));
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
glBlendFunc(GL_ONE, GL_ONE);
|
||||
m_stars_vao->bind();
|
||||
@@ -620,9 +408,9 @@ void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, d
|
||||
void opengl46_weather::draw_sky(glm::mat4 const &Projection, glm::mat4 const &View, bool const Mainview)
|
||||
{
|
||||
int mode{0};
|
||||
if (m_clouds_drawn)
|
||||
if (m_state.clouds_drawn())
|
||||
{
|
||||
if (Mainview && m_clouds_view_valid)
|
||||
if (Mainview && m_state.clouds_view_valid())
|
||||
{
|
||||
mode = 1;
|
||||
glBindTextureUnit(gl::CLOUD_TEX, m_clouds_history[m_clouds_current]);
|
||||
@@ -674,28 +462,5 @@ std::string opengl46_weather::info_times()
|
||||
|
||||
void opengl46_weather::draw_debug_ui()
|
||||
{
|
||||
char const *fogmodes[]{"analytic (along the ray, as the old fog)", "volumetric"};
|
||||
ImGui::Combo("fog (gfx.fog)", &Global.gfx_fog, fogmodes, IM_ARRAYSIZE(fogmodes));
|
||||
char const *levels[]{"low", "medium", "high", "ultra"};
|
||||
auto fogquality{std::clamp(Global.gfx_fog_quality, 1, 4) - 1};
|
||||
if (ImGui::Combo("fog volume quality (gfx.fog.quality)", &fogquality, levels, IM_ARRAYSIZE(levels)))
|
||||
Global.gfx_fog_quality = fogquality + 1;
|
||||
ImGui::SliderFloat("fog volume depth, m (gfx.fog.range)", &Global.gfx_fog_range, 100.f, 10000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("lamps in the fog (gfx.fog.lights)", &Global.gfx_fog_lights, 0.f, 4.f);
|
||||
ImGui::Checkbox("shadows of the sun and headlights in the fog (gfx.fog.shadows)", &Global.gfx_fog_shadows);
|
||||
ImGui::Checkbox("fog blended over frames (gfx.fog.temporal)", &Global.gfx_fog_temporal);
|
||||
ImGui::SliderFloat("share of a new frame in the fog (gfx.fog.temporal.share)", &Global.gfx_fog_temporal_share, 0.02f, 1.f);
|
||||
char const *cloudlevels[]{"none", "low", "medium", "high", "ultra"};
|
||||
ImGui::Combo("clouds (gfx.clouds)", &Global.gfx_clouds, cloudlevels, IM_ARRAYSIZE(cloudlevels));
|
||||
ImGui::SliderFloat("cloud distance, m (gfx.clouds.distance)", &Global.gfx_clouds_distance, 5000.f, 200000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::Checkbox("cloud shadows (gfx.clouds.shadows)", &Global.gfx_clouds_shadows);
|
||||
ImGui::SliderFloat("cloud shadow map, m (gfx.clouds.shadows.range)", &Global.gfx_clouds_shadows_range, 1000.f, 100000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("cloud sunlight (tuning, not saved)", &m_cloud_sun_scale, 0.25f, 3.f);
|
||||
ImGui::SliderFloat("cloud sky light (tuning, not saved)", &m_cloud_sky_scale, 0.f, 3.f);
|
||||
ImGui::SliderFloat("cloud edge detail (tuning, not saved)", &m_cloud_detail_scale, 0.f, 3.f);
|
||||
ImGui::Checkbox("stars (gfx.stars)", &Global.gfx_stars);
|
||||
ImGui::SliderFloat("star brightness", &Global.gfx_stars_brightness, 0.f, 8.f);
|
||||
ImGui::Text("fog range %.0f m, sun share of the fog light %.2f, clouds let through %.2f on average", m_fog_range, m_sun_share, m_average_transmittance);
|
||||
ImGui::Separator();
|
||||
ui::weather_controls(simulation::Environment.weather());
|
||||
m_state.draw_debug_ui();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/vao.h"
|
||||
#include "gl/buffer.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/common/weatherstate.h"
|
||||
|
||||
class opengl46_sky;
|
||||
|
||||
@@ -64,22 +65,22 @@ class opengl46_weather
|
||||
// true when the volumetric clouds are drawn (the sky leaves its own overcast look then)
|
||||
bool clouds_drawn() const
|
||||
{
|
||||
return m_clouds_drawn;
|
||||
return m_state.clouds_drawn();
|
||||
}
|
||||
// true when the clouds cast shadows (the sunlight isn't dimmed by the overcast then, the shadows do it)
|
||||
bool cloud_shadows() const
|
||||
{
|
||||
return m_shadows_valid;
|
||||
return m_state.cloud_shadows();
|
||||
}
|
||||
// fog density of a regular draw (model_ubs.fog_density): 1 / the fog range
|
||||
float fog_density() const
|
||||
{
|
||||
return 1.f / m_fog_range;
|
||||
return m_state.fog_density();
|
||||
}
|
||||
// distance the fog hides things at, for the draw range: a dark object fades to about 2% there
|
||||
float fog_range() const
|
||||
{
|
||||
return m_fog_range;
|
||||
return m_state.fog_range();
|
||||
}
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
@@ -87,16 +88,7 @@ class opengl46_weather
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
struct quality_level
|
||||
{
|
||||
int divisor; // of the resolution of the clouds of the main view
|
||||
int steps; // samples through a layer at most
|
||||
int light_steps; // samples towards the sun
|
||||
bool detail; // with the small noise
|
||||
glm::ivec2 panorama; // size of the panorama
|
||||
glm::ivec3 fog_grid; // cells of the fog volume
|
||||
};
|
||||
static quality_level quality(int const Clouds, int const Fog);
|
||||
using quality_level = gfx::weather_state::quality_level;
|
||||
|
||||
static GLuint create_texture(GLenum Target, GLenum Format, glm::ivec3 const &Size, GLint Filter, GLint Wrap);
|
||||
static void release(GLuint &Texture);
|
||||
@@ -119,8 +111,8 @@ class opengl46_weather
|
||||
std::unique_ptr<gl::program> m_stars_program;
|
||||
|
||||
std::unique_ptr<gl::ubo> m_weather_ubo;
|
||||
gl::weather_ubs m_weather{};
|
||||
bool m_horizon_on_gpu{false};
|
||||
// the block and the decisions of the frame, shared with the vulkan renderer
|
||||
gfx::weather_state m_state;
|
||||
|
||||
// noise
|
||||
GLuint m_shape_noise{0};
|
||||
@@ -130,7 +122,6 @@ class opengl46_weather
|
||||
// shadows of the clouds
|
||||
GLuint m_shadow_map{0};
|
||||
int m_shadow_size{0};
|
||||
bool m_shadows_valid{false};
|
||||
gl::gpu_timer m_shadow_timer;
|
||||
|
||||
// clouds of the main view
|
||||
@@ -140,51 +131,21 @@ class opengl46_weather
|
||||
GLuint m_clouds_depth{0};
|
||||
std::array<GLuint, 2> m_clouds_history{0, 0};
|
||||
int m_clouds_current{0}; // the history holding the latest result
|
||||
bool m_clouds_view_valid{false}; // render_view() made the clouds of the frame
|
||||
gl::gpu_timer m_clouds_timer;
|
||||
// panorama
|
||||
GLuint m_panorama{0};
|
||||
glm::ivec2 m_panorama_size{0};
|
||||
int m_panorama_band{0};
|
||||
bool m_panorama_full{false}; // every band was drawn once since it was made
|
||||
gl::gpu_timer m_panorama_timer;
|
||||
bool m_clouds_drawn{false};
|
||||
|
||||
// fog volume
|
||||
glm::ivec3 m_fog_grid{0};
|
||||
std::array<GLuint, 2> m_fog_scattering{0, 0};
|
||||
int m_fog_current{0};
|
||||
GLuint m_fog_integrated{0};
|
||||
bool m_fog_volume_valid{false};
|
||||
float m_fog_range{5000.f};
|
||||
glm::vec4 m_fog_slicing{0.f};
|
||||
gl::gpu_timer m_fog_timer;
|
||||
|
||||
// the camera of the previous frame of the main view, for the blending over frames
|
||||
struct view_state
|
||||
{
|
||||
glm::mat4 projection_view{1.f}; // world axes to clip space, camera at the origin
|
||||
glm::dvec3 camera{0.0};
|
||||
glm::vec4 slicing{0.f};
|
||||
bool valid{false};
|
||||
};
|
||||
view_state m_previous_clouds;
|
||||
view_state m_previous_fog;
|
||||
int m_frame{0};
|
||||
|
||||
// stars
|
||||
std::optional<gl::vao> m_stars_vao;
|
||||
std::optional<gl::buffer> m_stars_buffer;
|
||||
int m_stars_count{0};
|
||||
|
||||
// tuning of the look of the clouds in the debug window, not saved: scales of their sunlight, of their sky light and of the
|
||||
// erosion of their edges by the detail noise (weather_sun_color.w, weather_ambient.w, cloud_wind.w)
|
||||
float m_cloud_sun_scale{1.f};
|
||||
float m_cloud_sky_scale{1.f};
|
||||
float m_cloud_detail_scale{1.f};
|
||||
// what the debug window shows
|
||||
float m_sun_share{0.f};
|
||||
float m_average_transmittance{1.f};
|
||||
std::string m_report;
|
||||
char m_report_input[256]{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user