17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 21:44:53 +01:00
Files
maszyna/rendering/common/scenewalkindirect.cpp

830 lines
29 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the part of the shared walk of the scene which feeds the gpu culling of the instances and records the draws made from its
// result (stage N6c of claude/vulkan14-renderer-plan.md): the records of the sleepers and of the models drawn on their own, the
// queueing of buckets, the walks of the submodel trees of the culled groups and the order of the recorded draws
#include "stdafx.h"
#include "rendering/common/scenewalk.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
namespace gfx
{
namespace
{
// the group of the sleepers of a track in the gpu culling: their model, and the skin in the slot Render_Sleepers() sets it in
scene::basic_cell::instance_bucket_key sleeper_key(TTrack const &Track)
{
scene::basic_cell::instance_bucket_key key;
key.pModel = Track.m_sleeper_model;
key.skins[1] = Track.m_sleeper_skin;
return key;
}
} // namespace
void scene_walk::Begin_Pass_Cull(gfx::instance_cull *Cull, bool const Mainview)
{
m_passcull = Cull;
m_instancecullpass = false;
if (m_passcull != nullptr)
{
m_instancecullpass = true;
// the same choices as in Render_Instanced()
auto const reflections{m_renderpass.draw_mode == rendermode::reflections};
gfx::instance_cull::pass_setup setup;
setup.frame = m_framestamp;
setup.camera = m_renderpass.pass_camera.position();
setup.distancecamera = (m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera.position() : m_renderpass.pass_camera.position());
setup.frustum = &m_renderpass.pass_camera.frustum();
setup.distancescale = (reflections ? 1.f : static_cast<float>(1.0 / (static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.fDistanceFactor))));
setup.distanceoffset = (reflections ? static_cast<float>(sq(EU07_REFLECTIONFIDELITYOFFSET)) : 0.f);
setup.distancerange = (reflections ? static_cast<float>(sq(Global.reflectiontune.range_instances)) : std::numeric_limits<float>::max());
setup.drawrange = static_cast<float>(sq(m_renderpass.draw_range + 250));
setup.zoom = Global.ZoomFactor;
setup.drawing = (Global.gfx_gpucull == 1);
m_passcull->begin_pass(setup);
}
// the sleepers of tracks go with the instances of the main view
m_sleeperspass = (Mainview && (m_passcull != nullptr) && Global.gfx_gpucull_sleepers);
if (m_sleeperspass && (false == m_passcull->drawing()))
{
for (auto &counts : m_sleeperbandcounts)
{
counts.second.clear();
}
}
// the models drawn on their own go with the instances of every pass culled on the gpu
m_modelspass = (m_instancecullpass && Global.gfx_gpucull_models);
}
void scene_walk::Compare_Pass_Cull()
{
// the sleepers the cpu drew along the tracks, before the culling ran
if (m_sleeperspass)
{
for (auto const &counts : m_sleeperbandcounts)
{
if (false == counts.second.empty())
{
m_passcull->cpu_result(counts.first, counts.second, true);
}
}
}
// and the models drawn on their own
for (auto const &model : m_modelcpudistances)
{
m_passcull->cpu_instance(model.first, model.second);
}
}
void scene_walk::End_Pass_Cull()
{
if (m_instancecullpass)
{
m_passcull->end_pass();
m_instancecullpass = false;
m_passcull = nullptr;
}
m_sleeperspass = false;
m_modelspass = false;
}
bool scene_walk::walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
{
if (m_instancecullpass)
{
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
// sun shadows are left out of them, as Render_Instanced() leaves them
if ((m_renderpass.draw_mode != rendermode::shadows) || Instances_Cast_Shadow(Key, Bucket))
{
m_passcull->add_bucket(Key, Bucket, (Bucketrange ? Bucket_Area(Bucket) : Area));
}
// and when the instances are drawn from its result, the cpu has nothing more to do with them
if (m_passcull->drawing())
{
return false;
}
}
// the depth pass of the whole scene culled them already, the colour pass draws what it recorded
if (m_indirectcolourpending)
{
return false;
}
return true;
}
void scene_walk::walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket)
{
if (m_instancecullpass)
{
m_passcull->skip_bucket(Bucket);
}
}
bool scene_walk::walk_queue_model(TAnimModel *Instance, bool const Inrange)
{
return Queue_Model(Instance, Inrange);
}
bool scene_walk::walk_queue_sleepers(TTrack *Track)
{
if (m_sleeperspass)
{
// culled with the instances of the pass and drawn from the result, or drawn by the cpu as well to compare
Queue_Sleepers(Track);
if (m_passcull->drawing())
{
return true;
}
}
else if (m_indirectcolourpending && m_indirectcoloursleepers)
{
// the depth pass of the whole scene culled and recorded them, the colour pass draws what it recorded
return true;
}
return false;
}
void scene_walk::walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn)
{
if (m_sleeperspass && m_instancecullpass && (false == m_passcull->drawing()))
{
// the comparison with the gpu culling: the sleepers drawn by lod band, the bands as the culling makes them, split by
// the bounds of the model past 0 and short of the largest float
auto const counted = [](float const Bound) {
return (Bound > 0.f) && (Bound < std::numeric_limits<float>::max());
};
auto &counts{m_sleeperbandcounts[sleeper_key(Track)]};
auto const bands{static_cast<std::size_t>(std::count_if(Lodbounds.begin(), Lodbounds.end(), counted)) + 1};
if (counts.size() < bands)
{
counts.resize(bands, 0);
}
for (auto const &survivor : Drawn)
{
std::size_t band{0};
for (auto const bound : Lodbounds)
{
if (counted(bound) && (bound <= survivor.first))
{
++band;
}
}
++counts[band];
}
}
}
void scene_walk::walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts)
{
m_passcull->cpu_result(Key, Bandcounts);
}
void scene_walk::Queue_Sleepers(TTrack *Track)
{
// the gates of Render_Sleepers(); the range is tested for the whole track
if ((Track->m_sleeper_model == nullptr) || Track->m_sleeper_local_transforms.empty() || (Global.SleeperDistance <= 0.f))
{
return;
}
auto const distsq{glm::length2(Track->location() - m_renderpass.pass_camera.position())};
auto const cutoffsq{static_cast<double>(Global.SleeperDistance) * static_cast<double>(Global.SleeperDistance)};
if (distsq > cutoffsq)
{
return;
}
auto &records{m_sleeperrecords[Track]};
auto const &transforms{Track->m_sleeper_local_transforms};
if ((records.bucket.recordsversion != m_instancerecordsversion) || (records.model != Track->m_sleeper_model) || (records.skin != Track->m_sleeper_skin) ||
(records.transforms != transforms.data()) || (records.count != transforms.size()))
{
// each sleeper is a record placed and turned by its transform, relative to the origin of the track (the centre of its
// cell), with the sphere Render_Sleepers() tests against the frustum
auto const radius{std::max(Track->m_sleeper_model->bounding_radius(), 2.0f)};
auto &bucket{records.bucket};
bucket.records.clear();
bucket.records.reserve(transforms.size());
glm::vec3 low{std::numeric_limits<float>::max()};
glm::vec3 high{std::numeric_limits<float>::lowest()};
for (auto const &local : transforms)
{
scene::basic_cell::instance_record record;
glm::vec3 const position{local[3]};
record.position = packed_vec3{position};
record.radius = radius;
record.rangesquaredmin = 0.f;
record.rangesquaredmax = std::numeric_limits<float>::max();
record.flags = scene::basic_cell::instance_visible | scene::basic_cell::instance_instanceable;
for (int column = 0; column < 3; ++column)
{
record.rotationscale[column] = local[column];
}
bucket.records.emplace_back(record);
low = glm::min(low, position);
high = glm::max(high, position);
}
bucket.recordorigin = Track->m_origin;
bucket.lodrangesquared = std::numeric_limits<float>::max();
bucket.recordsversion = m_instancerecordsversion;
bucket.gpustale = true;
records.key = sleeper_key(*Track);
records.area = scene::bounding_area{Track->m_origin + glm::dvec3{(low + high) * 0.5f}, 0.5f * glm::length(high - low)};
records.model = Track->m_sleeper_model;
records.skin = Track->m_sleeper_skin;
records.transforms = transforms.data();
records.count = transforms.size();
}
records.frame = m_framestamp;
m_passcull->add_sleepers(records.key, records.bucket, records.area);
}
// queues a model instance drawn on its own in the gpu culling of the pass, as a bucket and a group of its own. the culling
// makes the tests of Render( TAnimModel * ): visibility, frustum, the range of the instance, the draw range, the size on the
// screen, and gives it the lod band of the bounds of its submodels (mindistance/maxdistance) its distance falls in, then
// Walk_Indirect_Draws() animates, prepares and walks its submodel tree from its origin, like it does for a group of
// instanced ones
bool scene_walk::Queue_Model(TAnimModel *Instance, bool const Inrange)
{
// a billboard is turned to the camera from the modelview of the instance, which a walk from its origin doesn't have
if ((false == Instance->m_cameraindependent) || (Instance->Model() == nullptr))
{
return false;
}
if (false == m_modelspass)
{
// the depth pass of the whole scene culled and recorded it, the colour pass draws what it recorded
return (m_indirectcolourpending && m_indirectcolourmodels);
}
auto const drawing{m_passcull->drawing()};
// a model hidden by an event, or in a cell wholly out of the range of the pass, would be left out by the culling
if ((false == Instance->visible()) || (false == Inrange))
{
return drawing;
}
auto &records{m_modelrecords[Instance]};
auto &bucket{records.bucket};
if ((bucket.recordsversion != m_instancerecordsversion) || (bucket.records.size() != 1))
{
// the key, the instance and the record anew: the editor may have given the instance another model or skins, or
// another instance may have taken the place of a deleted one
auto &key{records.key};
key = {};
key.pModel = Instance->Model();
if (auto const *material{Instance->Material()})
{
for (int skin = 0; skin < 5; ++skin)
{
key.skins[skin] = material->replacable_skins[skin];
}
}
key.instance = Instance;
bucket.instances.assign(1, Instance);
// the location of the instance as the origin of the record, which keeps the precision
Update_Instance_Records(key.pModel, bucket, Instance->location());
// drawn by its bucket, though it isn't instanceable
bucket.records.front().flags |= scene::basic_cell::instance_instanceable;
records.area = scene::bounding_area{Instance->location(), 0.f};
}
records.frame = m_framestamp;
if (m_renderpass.draw_mode == rendermode::shadows)
{
// a model with nothing to draw in the shadows is left out of them, like the instances. the answer depends on the
// model and the skins only, and is shared by the instances with them
auto modelkey{records.key};
modelkey.instance = nullptr;
if (false == Instances_Cast_Shadow(modelkey, bucket))
{
return drawing;
}
}
// out of the frustum of the pass, beyond its range or too small on the screen by its record: the culling would leave it out,
// and the walk of Walk_Indirect_Draws() isn't made for it
if (Global.gfx_gpucull_bucketrange && (false == EditorModeFlag) && Bucket_Culled(bucket))
{
m_passcull->skip_bucket(bucket);
return drawing;
}
m_passcull->add_bucket(records.key, bucket, records.area);
if (false == drawing)
{
// the cpu draws it too, and tells what it drew for the comparison
m_modelcomparekey = &records.key;
}
return drawing;
}
bool scene_walk::Instances_Cast_Shadow(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket)
{
auto const lookup{m_shadowcasters.find(Key)};
if (lookup != m_shadowcasters.end())
{
return lookup->second;
}
// the skins of the instance Render_Instanced() would take them from
TAnimModel const *instance{nullptr};
for (auto const *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
instance = candidate;
break;
}
}
auto const *skins{instance != nullptr && instance->Material() != nullptr ? instance->Material()->replacable_skins : nullptr};
auto castsshadow{false};
m_instance_lodpending.clear();
m_instance_lodpending.push_back(Key.pModel != nullptr ? Key.pModel->Root : nullptr);
while ((false == castsshadow) && (false == m_instance_lodpending.empty()))
{
auto const *submodel = m_instance_lodpending.back();
m_instance_lodpending.pop_back();
if (submodel == nullptr)
{
continue;
}
if (submodel->eType < TP_ROTATOR)
{
auto const material{submodel->m_material >= 0 ? submodel->m_material : (skins != nullptr ? skins[-submodel->m_material] : null_handle)};
castsshadow = (walk_material_data(material).shadow_rank <= Global.gfx_shadow_rank_cutoff);
}
m_instance_lodpending.push_back(submodel->Child);
m_instance_lodpending.push_back(submodel->Next);
}
m_shadowcasters.emplace(Key, castsshadow);
return castsshadow;
}
void scene_walk::Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade)
{
m_indirectdraws.clear();
m_indirectcommands.clear();
auto const &lodbounds{Cull.lod_bounds()};
// the cascades of the sun shadows make the same walks: the mode, the lod band and the state of the models are the same in all
// of them within a frame, only the instances differ, and those the gpu fills in. the walk of a group in a band is made by the
// first cascade which draws it, the others copy its draws and commands with entries of their own
auto const cascade{(m_renderpass.draw_mode == rendermode::shadows) && Cascade};
auto constexpr nowalk{std::numeric_limits<std::uint32_t>::max()};
if (cascade && (m_cascaderecordframe != m_framestamp))
{
m_cascaderecordframe = m_framestamp;
m_cascadedraws.clear();
m_cascadecommands.clear();
if ((m_framestamp % 256) == 0)
{
// the groups not drawn for a while
for (auto records{m_cascaderecords.begin()}; records != m_cascaderecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_cascaderecords.erase(records);
}
else
{
++records;
}
}
}
}
if (m_walkcountsframe != m_framestamp)
{
m_walkcountsframe = m_framestamp;
m_lastwalkcounts = m_walkcounts;
m_walkcounts = {};
}
auto const walkstart{std::chrono::steady_clock::now()};
// the walks of earlier frames. they go when the switch is off, as the models may change meanwhile without the walks noticing
// it the next time (the season), and when the shadows take other submodels
auto const walkcache{Global.gfx_gpucull_walkcache && (false == m_previewing)};
if ((false == Global.gfx_gpucull_walkcache) || (m_walkrankcutoff != Global.gfx_shadow_rank_cutoff))
{
m_walkrecords.clear();
m_walkrankcutoff = Global.gfx_shadow_rank_cutoff;
}
if (walkcache && ((m_framestamp % 256) == 0))
{
// the groups not drawn for a while
for (auto records{m_walkrecords.begin()}; records != m_walkrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_walkrecords.erase(records);
}
else
{
++records;
}
}
}
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
m_indirectrecording = true;
for (auto const &group : Cull.draw_groups())
{
auto *model{group.key->pModel};
if ((model == nullptr) || (model->Root == nullptr) || ((group.instance == nullptr) && (false == group.sleepers)))
{
continue;
}
// a model drawn on its own is prepared with its lights and animations each time; the others are prepared only for a walk.
// the version is taken before, so a walk prepared by something which changes the model isn't taken up again
walk_record *kept{nullptr};
if (walkcache && (group.key->instance == nullptr))
{
kept = &m_walkrecords[*group.key];
kept->frame = m_framestamp;
auto &bands{kept->bands[shadows ? 1 : 0]};
if (bands.size() != group.lodcount + 1)
{
bands.clear();
bands.resize(group.lodcount + 1);
}
}
auto const modelversion{model->state_version()};
material_handle const *skins{nullptr};
auto alpha{0x30300030};
auto prepared{false};
auto const prepare = [&]() {
prepared = true;
if (group.sleepers)
{
// as Render_Sleepers() sets them up: the skin of the track, if any. like scenery models they pick no texture
// variants of their own
if (group.key->skins[1] != null_handle)
{
skins = group.key->skins.data();
}
TSubModel::iVariantSeed = 0;
}
else
{
if (group.key->instance != nullptr)
{
// a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up
group.instance->RaAnimate(m_framestamp);
}
group.instance->RaPrepare();
auto const *material{group.instance->Material()};
if (material != nullptr)
{
skins = material->replacable_skins;
alpha = material->textures_alpha;
}
}
alpha ^= 0x0F0F000F;
};
// the walks of the group the cascades made in this frame. a group whose bands were all walked already isn't prepared
cascade_record *walks{nullptr};
if (cascade)
{
walks = &m_cascaderecords[*group.key];
if ((walks->frame != m_framestamp) || (walks->bands.size() != group.lodcount + 1))
{
walks->frame = m_framestamp;
walks->bands.assign(group.lodcount + 1, cascade_band{nowalk, 0});
}
}
else if (kept == nullptr)
{
prepare();
}
for (std::uint32_t band = 0; band <= group.lodcount; ++band)
{
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
auto const bandmin{band == 0 ? 0.f : lodbounds[group.lodfirst + band - 1]};
auto const bandmax{band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[group.lodfirst + band]};
if ((bandmin > group.distancemax) || (bandmax <= group.distancemin))
{
continue;
}
m_indirectentry = group.entryfirst + band;
if ((walks != nullptr) && (walks->bands[band].first != nowalk))
{
// walked by another cascade: its draws, for the instances of this entry
auto const &walk{walks->bands[band]};
m_indirectdraws.insert(m_indirectdraws.end(), m_cascadedraws.begin() + walk.first, m_cascadedraws.begin() + walk.first + walk.count);
for (std::uint32_t draw = 0; draw < walk.count; ++draw)
{
auto &command{m_indirectcommands.emplace_back(m_cascadecommands[walk.first + draw])};
command.entry = m_indirectentry;
}
// as the walk would count them
m_renderpass.draw_stats.submodels += static_cast<int>(walk.count);
m_renderpass.draw_stats.drawcalls += static_cast<int>(walk.count);
continue;
}
auto const walkfirst{m_indirectdraws.size()};
auto *keptwalk{kept != nullptr ? &kept->bands[shadows ? 1 : 0][band] : nullptr};
if ((keptwalk != nullptr) && (keptwalk->valid) && (keptwalk->bandmin == bandmin) && (keptwalk->modelversion == modelversion) &&
(keptwalk->blending == walk_blending_enabled()))
{
// walked in an earlier frame, with the model as it is now
Replay_Walk(*keptwalk, shadows);
++m_walkcounts.reused;
}
else
{
if (false == prepared)
{
prepare();
}
auto const blending{walk_blending_enabled()};
m_indirectchanging = false;
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet(skins, alpha);
model->Root->pRoot = model;
Render(model->Root);
::glPopMatrix();
++m_walkcounts.walked;
if (m_indirectchanging)
{
++m_walkcounts.changing;
}
if (keptwalk != nullptr)
{
keptwalk->valid = (false == m_indirectchanging);
if (keptwalk->valid)
{
keptwalk->draws.assign(m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
keptwalk->bandmin = bandmin;
keptwalk->modelversion = modelversion;
keptwalk->blending = blending;
}
else
{
keptwalk->draws.clear();
}
}
}
if (walks != nullptr)
{
walks->bands[band] = cascade_band{static_cast<std::uint32_t>(m_cascadedraws.size()), static_cast<std::uint32_t>(m_indirectdraws.size() - walkfirst)};
m_cascadedraws.insert(m_cascadedraws.end(), m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
m_cascadecommands.insert(m_cascadecommands.end(), m_indirectcommands.begin() + walkfirst, m_indirectcommands.end());
}
}
}
m_indirectrecording = false;
m_walkcounts.walktime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - walkstart).count();
}
void scene_walk::Record_Indirect_Draw(gfx::geometry_handle const &Geometry)
{
// the walk of Walk_Indirect_Draws(): the draw is made later, from the gpu culling, with this state
auto &geometry{walk_geometry()};
gfx::indirect_source source;
if (geometry.indirect_source(Geometry, source))
{
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
m_indirectdraws.push_back({model_ubs, m_indirectmaterial, m_indirectsubmodel, Geometry, source.type, walk_blending_enabled(), geometry.draw_group(Geometry)});
}
}
void scene_walk::Replay_Walk(walk_band const &Walk, bool const Shadows)
{
auto &geometry{walk_geometry()};
for (auto const &walkdraw : Walk.draws)
{
// the place of the chunk in its bank is asked for again: the bank may have been let go since and made anew, which the
// question (as the walk) takes care of, uploading the chunk
gfx::indirect_source source;
if (false == geometry.indirect_source(walkdraw.geometry, source))
{
continue;
}
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
auto &draw{m_indirectdraws.emplace_back(walkdraw)};
draw.type = source.type;
draw.group = geometry.draw_group(walkdraw.geometry);
// the walk sets the placement of the submodel, and before each draw of the colour passes the parameters of its material
// (the material bind); the rest of the block is what the pass has at the moment, as the walk would leave it
auto const &walked{walkdraw.ubs};
draw.ubs = model_ubs;
draw.ubs.modelview = walked.modelview;
draw.ubs.modelviewnormal = walked.modelviewnormal;
if (false == Shadows)
{
std::memcpy(&draw.ubs.param[0], &walked.param[0], sizeof(draw.ubs.param));
draw.ubs.opacity = walked.opacity;
draw.ubs.alpha_mult = walked.alpha_mult;
}
}
// as the walk would count them
m_renderpass.draw_stats.submodels += static_cast<int>(Walk.draws.size());
m_renderpass.draw_stats.drawcalls += static_cast<int>(Walk.draws.size());
}
texture_handle scene_walk::Indirect_Shadow_Texture(material_handle const Material) const
{
if (Material == null_handle)
{
return null_handle;
}
return walk_material_data(Material).textures[0];
}
// the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same
// state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry,
// the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on
// the later one being drawn over the earlier one. the draws of different entries are drawn for different instances, which
// the gpu draws in no particular order anyway, so they're free to move. the entries are queues of draws; the draws at the
// heads of the queues which have the state taken next all go, each queue as far as its draws keep that state, then the
// queues wait for the state of their next draw. the states go in the order in which the queues first reach them
void scene_walk::Order_Indirect_Draws()
{
auto const count{m_indirectdraws.size()};
if (count < 2)
{
return;
}
// a number for each state the draws have, by sorting the states (the buffers are kept between frames)
m_indirectstatekeys.clear();
// the sun shadows draw with the program and the texture of the alpha test, whatever the material
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
for (std::size_t index = 0; index < count; ++index)
{
auto const &draw{m_indirectdraws[index]};
if (shadows)
{
m_indirectstatekeys.push_back({Indirect_Shadow_Texture(draw.material), 0u, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
continue;
}
auto variant{0u};
if (draw.material != null_handle)
{
auto const &material{walk_material_data(draw.material)};
if (false == material.texture_variants.empty())
{
variant = TSubModel::VariantId(draw.submodel) % static_cast<std::uint32_t>(material.texture_variants.size());
}
}
m_indirectstatekeys.push_back({draw.material, variant, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
}
std::sort(std::begin(m_indirectstatekeys), std::end(m_indirectstatekeys), [](indirect_state_key const &Left, indirect_state_key const &Right) { return Left.tie() < Right.tie(); });
m_indirectdrawstates.resize(count);
std::uint32_t statecount{0};
for (std::size_t index = 0; index < count; ++index)
{
if ((index > 0) && (m_indirectstatekeys[index].tie() != m_indirectstatekeys[index - 1].tie()))
{
++statecount;
}
m_indirectdrawstates[m_indirectstatekeys[index].draw] = statecount;
}
++statecount;
if (statecount == count)
{
// no two draws share a state, nothing would go together
return;
}
auto constexpr none{std::numeric_limits<std::uint32_t>::max()};
m_indirectstates.assign(statecount, indirect_state{none, none});
m_indirectstateorder.clear();
m_indirectqueues.clear();
// the queue waits for the state of its next draw, behind the queues waiting for it already
auto const enqueue = [this, none](std::uint32_t const Queue) {
auto &queue{m_indirectqueues[Queue]};
auto &state{m_indirectstates[m_indirectdrawstates[queue.next]]};
queue.link = none;
if (state.head == none)
{
state.head = Queue;
m_indirectstateorder.push_back(m_indirectdrawstates[queue.next]);
}
else
{
m_indirectqueues[state.tail].link = Queue;
}
state.tail = Queue;
};
for (std::size_t index = 0; index < count;)
{
auto end{index + 1};
while ((end < count) && (m_indirectcommands[end].entry == m_indirectcommands[index].entry))
{
++end;
}
m_indirectqueues.push_back({static_cast<std::uint32_t>(index), static_cast<std::uint32_t>(end), none});
index = end;
}
for (std::uint32_t queue = 0; queue < m_indirectqueues.size(); ++queue)
{
enqueue(queue);
}
m_indirectorder.clear();
for (std::size_t taken = 0; taken < m_indirectstateorder.size(); ++taken)
{
auto const stateindex{m_indirectstateorder[taken]};
auto queueindex{m_indirectstates[stateindex].head};
m_indirectstates[stateindex] = {none, none};
while (queueindex != none)
{
auto &queue{m_indirectqueues[queueindex]};
auto const nextqueue{queue.link};
while ((queue.next < queue.end) && (m_indirectdrawstates[queue.next] == stateindex))
{
m_indirectorder.push_back(queue.next);
++queue.next;
}
if (queue.next < queue.end)
{
enqueue(queueindex);
}
queueindex = nextqueue;
}
}
m_indirectdrawsordered.clear();
m_indirectcommandsordered.clear();
for (auto const index : m_indirectorder)
{
m_indirectdrawsordered.push_back(m_indirectdraws[index]);
m_indirectcommandsordered.push_back(m_indirectcommands[index]);
}
m_indirectdraws.swap(m_indirectdrawsordered);
m_indirectcommands.swap(m_indirectcommandsordered);
}
bool scene_walk::Indirect_Mergeable(std::size_t const First, std::size_t const Other) const
{
auto const &first{m_indirectdraws[First]};
auto const &other{m_indirectdraws[Other]};
if ((first.material != other.material) || (first.blending != other.blending) || (first.type != other.type) || (first.group != other.group) ||
(m_indirectcommands[First].indexed != m_indirectcommands[Other].indexed))
{
return false;
}
if (first.material == null_handle)
{
return true;
}
// the texture set the material bind binds
auto const &material{walk_material_data(first.material)};
if (material.texture_variants.empty())
{
return true;
}
return (&material.GetTextures(TSubModel::VariantId(first.submodel)) == &material.GetTextures(TSubModel::VariantId(other.submodel)));
}
void scene_walk::Expire_Indirect_Records()
{
if ((m_framestamp % 256) == 0)
{
// the sleeper records of tracks out of sight for a while. their places in the record buffer of the gpu culling are
// dropped when it starts over
for (auto records{m_sleeperrecords.begin()}; records != m_sleeperrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_sleeperrecords.erase(records);
}
else
{
++records;
}
}
// and so are the records of the models drawn on their own
for (auto records{m_modelrecords.begin()}; records != m_modelrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_modelrecords.erase(records);
}
else
{
++records;
}
}
}
}
void scene_walk::Clear_Indirect_Records()
{
m_sleeperrecords.clear();
m_modelrecords.clear();
m_cascaderecords.clear();
m_walkrecords.clear();
}
} // namespace gfx