optimizations + bugfixes
* Bombs explode on damage/explosion * Orbits spell fixed * Performance: implemented shared component indexes, compiled Lua caching, and updates only for changed projectile fields. Also removed accumulating hidden entity slots.
This commit is contained in:
1 parent
87bff164f1
commit
a34f42d53e
17 files changed
+727
-131
No files matched your search
@@ -153,6 +153,7 @@ public:
|
||||
void remove(int item) {
|
||||
const Loc l = where(item);
|
||||
take(item);
|
||||
if (item && gun_.equipped() == item) gun_.equip(0);
|
||||
(void)l;
|
||||
}
|
||||
|
||||
|
||||
+222
-54
@@ -18,6 +18,7 @@
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -80,6 +81,7 @@ void fly_waypoint(FlyPath& fp, float px, float py, float gx, float gy, int frame
|
||||
#include "app/inventory.h"
|
||||
#include "entity/status.h"
|
||||
#include "gun/gpu_fx.h"
|
||||
#include "gun/projectile_damage.h"
|
||||
#include "gun/projectiles.h"
|
||||
#include "physics/gpu_bodies.h"
|
||||
#include "phys/phys_system.h"
|
||||
@@ -1763,7 +1765,7 @@ int main(int argc, char** argv) {
|
||||
// ---- entity runtime <-> GPU: slots, hooks, uploads of entities spawned by scripts ----
|
||||
std::vector<int> slotToId(kMaxEnts, 0);
|
||||
struct CollFx { double splash = 0; int mat = -1, cells = 0; }; // solid_on_collision_splash_power, solid_on_collision_material, pixels of the body
|
||||
struct ScriptShot { std::string file; int shooter; double x, y, tx, ty; bool send = true; };
|
||||
struct ScriptShot { std::string file; int shooter; double x, y, tx, ty; bool send = true; int entity = 0; };
|
||||
std::vector<ScriptShot> pendingShots; // GameShootProjectile calls of this tick
|
||||
std::map<std::string, int> scriptShotNode; // file -> persistent node of the GPU projectile system
|
||||
// the game effects (GameEffectComponent children) of creatures and the player, as the GPU sees them
|
||||
@@ -1888,8 +1890,21 @@ int main(int argc, char** argv) {
|
||||
std::vector<CutRule> cutRules;
|
||||
bool cutGrow = false;
|
||||
std::vector<std::array<float, 8>> pendingEdits; // (x, y, radius, max durability, mode, material, probability, 0)
|
||||
struct ProjTwin { int slot = -1, serial = 0, id = 0; std::string sig; bool dirty = false; }; // + sig: the twin's components without the live fields when the projectile's type was last set; dirty: a script changed a field of the type since // a GPU projectile with a runtime entity of its own (CellEater, MagicConvertMaterial, scripts...): ring slot, cast serial (d.w), entity id
|
||||
struct ProjTwin { int slot = -1, serial = 0, id = 0; std::string sig; bool dirty = false; int trailFrame = -1; double trailX = 0, trailY = 0; bool synced = false; std::array<float, 24> lastGpu{}; int lastShooter = 0; uint64_t syncRevision = uint64_t(-1), hostRevision = uint64_t(-1), shieldLayoutRevision = uint64_t(-1), shieldValueRevision = uint64_t(-1); std::vector<ng::RtComponent*> projectileComponents; bool velocityRotation = true, hasHostSystems = true, pendingRemoval = false, shieldNoOp = false; int shieldComponent = 0; double angularVelocity = 0; }; // GPU slot, cast serial, runtime entity and cached component layout / synchronized fields
|
||||
std::vector<ProjTwin> projTwins;
|
||||
std::set<int> deferredProjectileSlots;
|
||||
bool realizingProjectileSlots = false;
|
||||
size_t twinRevision = 0, indexedTwinRevision = size_t(-1);
|
||||
std::unordered_map<int, size_t> twinIndex;
|
||||
auto findTwin = [&](int id) -> ProjTwin* {
|
||||
if (indexedTwinRevision != twinRevision) {
|
||||
twinIndex.clear();
|
||||
for (size_t i = 0; i < projTwins.size(); ++i) twinIndex.emplace(projTwins[i].id, i);
|
||||
indexedTwinRevision = twinRevision;
|
||||
}
|
||||
const auto it = twinIndex.find(id);
|
||||
return it == twinIndex.end() || projTwins[it->second].pendingRemoval ? nullptr : &projTwins[it->second];
|
||||
};
|
||||
bool gpuLuaRecovering = false;
|
||||
struct PendingGpuLua { int entity, component; std::string file; };
|
||||
std::vector<PendingGpuLua> pendingGpuLua;
|
||||
@@ -2207,6 +2222,7 @@ int main(int argc, char** argv) {
|
||||
// radius, explosion damage, age, bounces; e shooter slot + 1, herd) are written into its record; every other field (on_death_explode, collide_with_world, penetrate_world,
|
||||
// damage_by_type, bounce_*, knockback_force, sprites...) belongs to the type: the projectile gets the type of a definition built from the twin's components (def_from).
|
||||
auto twinLiveField = [](const std::string& type, const std::string& key) -> int { // the FProj float the field lives in (index in the 24 floats of the record), -1 = a field of the type, -2 = not the GPU's
|
||||
if (key.rfind("_ng_", 0) == 0) return -2; // per-instance host clocks do not change GPU types or their emitters
|
||||
if (key.size() > 1 && key[0] == 'm' && std::isupper((unsigned char)key[1]) && key != "mWhoShot" && key != "mShooterHerdId" && key != "mVelocity.x" && key != "mVelocity.y") return -2; // the engine's own state
|
||||
if (type == "ProjectileComponent") {
|
||||
if (key == "damage") return 4;
|
||||
@@ -2247,27 +2263,44 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
return s;
|
||||
};
|
||||
auto twinFromGpu = [&](int id, const float* f) { // the live fields of the GPU projectile into the twin (the scripts read them: SPELLS_TO_POWER sums the damage)
|
||||
const int age = (int)f[10];
|
||||
for (int pc : rt->components(id, "ProjectileComponent", "", true)) {
|
||||
auto& at = rt->comp(pc)->attrs;
|
||||
at["damage"] = std::to_string(f[4]);
|
||||
at["lifetime"] = std::to_string(f[5] >= 1e8f ? -1 : std::max(0, (int)f[5] - age));
|
||||
if (!at.count("mStartingLifetime") || at["mStartingLifetime"].empty()) at["mStartingLifetime"] = std::to_string(f[5] >= 1e8f ? -1 : (int)f[5]); // (set at the launch: the lifetime it started with) // the frames left (the engine counts lifetime down; mStartingLifetime keeps the start)
|
||||
at["config_explosion.explosion_radius"] = std::to_string(f[8]);
|
||||
at["config_explosion.damage"] = std::to_string(f[9]);
|
||||
at["bounces_left"] = std::to_string((int)f[11]);
|
||||
const int ss = (int)f[18];
|
||||
at["mWhoShot"] = std::to_string(ss == 1 ? rt->player() : (ss > 1 && ss <= entN ? slotToId[ss - 1] : 0));
|
||||
at["mShooterHerdId"] = std::to_string((int)f[19]);
|
||||
auto twinFromGpu = [&](int id, const float* f, ProjTwin* twin = nullptr) { // the live fields of the GPU projectile into the twin (the scripts read them: SPELLS_TO_POWER sums the damage)
|
||||
ng::RtEntity* te = rt->ent(id);
|
||||
if (twin && te && twin->syncRevision != te->component_revision) twin->synced = false;
|
||||
std::vector<ng::RtComponent*> localComponents;
|
||||
if (!twin || !twin->synced) {
|
||||
auto& cached = twin ? twin->projectileComponents : localComponents;
|
||||
cached.clear();
|
||||
for (int cid : rt->components(id, "ProjectileComponent", "", true)) cached.push_back(rt->comp(cid));
|
||||
}
|
||||
for (int vc : rt->components(id, "VelocityComponent", "", true)) { auto& at = rt->comp(vc)->attrs; at["mVelocity.x"] = std::to_string(f[2] * 60.0f); at["mVelocity.y"] = std::to_string(f[3] * 60.0f); at["gravity_y"] = std::to_string(f[6] * 3600.0f); at["air_friction"] = std::to_string(f[7] * 60.0f); }
|
||||
if (ng::RtEntity* te = rt->ent(id)) { // the rotation (00c073b0): along the velocity (velocity_sets_rotation, default on), else turned by angular_velocity per second
|
||||
bool vsr = true; double av = 0;
|
||||
for (int pc : rt->components(id, "ProjectileComponent", "", true)) { auto& at = rt->comp(pc)->attrs; vsr = !at.count("velocity_sets_rotation") || at["velocity_sets_rotation"] != "0"; av = at.count("angular_velocity") ? std::atof(at["angular_velocity"].c_str()) : 0.0; }
|
||||
const auto& projectileComponents = twin ? twin->projectileComponents : localComponents;
|
||||
const int age = (int)f[10];
|
||||
const auto changed = [&](int field) { return !twin || !twin->synced || twin->lastGpu[field] != f[field]; };
|
||||
const int ss = (int)f[18], shooter = ss == 1 ? rt->player() : (ss > 1 && ss <= entN ? slotToId[ss - 1] : 0);
|
||||
auto syncFloat = [](auto& attrs, const char* key, float value) {
|
||||
const auto it = attrs.find(key);
|
||||
if (it == attrs.end() || it->second.empty() || std::strtof(it->second.c_str(), nullptr) != value) attrs[key] = std::to_string(value);
|
||||
};
|
||||
for (auto* component : projectileComponents) {
|
||||
auto& at = component->attrs;
|
||||
if (changed(4)) syncFloat(at, "damage", f[4]);
|
||||
component->gpu_lifetime = f[5] >= 1e8f ? -1 : std::max(0, (int)f[5] - age);
|
||||
component->gpu_lifetime_pending = true;
|
||||
if ((!twin || !twin->synced) && (!at.count("mStartingLifetime") || at["mStartingLifetime"].empty())) at["mStartingLifetime"] = std::to_string(f[5] >= 1e8f ? -1 : (int)f[5]);
|
||||
if (changed(8)) syncFloat(at, "config_explosion.explosion_radius", f[8]);
|
||||
if (changed(9)) syncFloat(at, "config_explosion.damage", f[9]);
|
||||
if (changed(11)) at["bounces_left"] = std::to_string((int)f[11]);
|
||||
if (changed(18) || twin->lastShooter != shooter) at["mWhoShot"] = std::to_string(shooter);
|
||||
if (changed(19)) at["mShooterHerdId"] = std::to_string((int)f[19]);
|
||||
}
|
||||
if (changed(2) || changed(3) || changed(6) || changed(7)) for (int vc : rt->components(id, "VelocityComponent", "", true)) { auto& at = rt->comp(vc)->attrs; syncFloat(at, "mVelocity.x", f[2] * 60.0f); syncFloat(at, "mVelocity.y", f[3] * 60.0f); syncFloat(at, "gravity_y", f[6] * 3600.0f); syncFloat(at, "air_friction", f[7] * 60.0f); }
|
||||
if (te) { // rotation parameters change only when scripts edit the type or component layout
|
||||
bool vsr = twin ? twin->velocityRotation : true; double av = twin ? twin->angularVelocity : 0;
|
||||
if (!twin || !twin->synced) for (auto* component : projectileComponents) { auto& at = component->attrs; vsr = !at.count("velocity_sets_rotation") || at["velocity_sets_rotation"] != "0"; av = at.count("angular_velocity") ? std::atof(at["angular_velocity"].c_str()) : 0.0; }
|
||||
if (vsr) { if (f[2] != 0.0f || f[3] != 0.0f) te->rot = std::atan2((double)f[3], (double)f[2]); }
|
||||
else te->rot += av / 60.0;
|
||||
if (twin) { twin->velocityRotation = vsr; twin->angularVelocity = av; twin->syncRevision = te->component_revision; }
|
||||
}
|
||||
if (twin) { std::copy(f, f + 24, twin->lastGpu.begin()); twin->lastShooter = shooter; twin->synced = true; }
|
||||
};
|
||||
auto spawnTwin = [&](const ng::ProjDef* pd, double x, double y) { // the projectile's entity with the shot's extra entities as its children (the engine loads the files of c.extra_entities into the projectile entity)
|
||||
const int t = rt->spawn(pd->file, x, y, 0, false);
|
||||
@@ -2280,6 +2313,19 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
return t;
|
||||
};
|
||||
auto addProjectileDamageProxy = [&](int id, int projectileSlot) {
|
||||
auto* e = rt->ent(id);
|
||||
const auto damage = rt->components(id, "DamageModelComponent", "", false);
|
||||
if (!e || damage.empty() || e->gpu >= 0 || entBuilder.layer().ents.size() >= kMaxEnts) return;
|
||||
const auto* dm = rt->comp(damage.front());
|
||||
const ng::EntityState state = ng::projectile_damage_proxy({dm->type, dm->attrs}, e->x, e->y, projectileSlot);
|
||||
const int slot = (int)entBuilder.layer().ents.size();
|
||||
entBuilder.layer().ents.push_back(state);
|
||||
e->gpu = slot; slotToId[slot] = id; entReadPtr[slot] = state;
|
||||
deathDone[slot] = 0; propDone[slot] = 0;
|
||||
entN = (int)entBuilder.layer().ents.size();
|
||||
queueUp(g.ents, (VkDeviceSize)slot * sizeof(ng::EntityState), &state, sizeof state);
|
||||
};
|
||||
auto twinToGpu = [&](int slot, int field, const ng::RtComponent* c) { // a live field a script wrote: into the GPU record
|
||||
auto num = [&](const char* k, double dv) { auto it = c->attrs.find(k); return it == c->attrs.end() || it->second.empty() ? dv : std::atof(it->second.c_str()); };
|
||||
const float* f = reinterpret_cast<const float*>(fprojReadPtr + (size_t)slot * 96);
|
||||
@@ -2471,13 +2517,14 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
ProjTwin tw; tw.slot = k; tw.serial = d[3]; tw.id = twin; tw.sig = twinSig(twin);
|
||||
projTwins.push_back(tw);
|
||||
++twinRevision;
|
||||
twinSerial[k] = d[3];
|
||||
if (std::getenv("NG_SCRIPTLOG")) std::printf("frame %d: lazy twin %d of projectile slot %d serial %d (%s) for tag '%s'%c", rt->frame(), twin, k, d[3], pd->file.c_str(), tag.c_str(), 10);
|
||||
}
|
||||
busy = false;
|
||||
};
|
||||
rt->on_components_enabled = [&, prevEnabled2 = rt->on_components_enabled](int id) { // a script switched a component of a twin on or off: its type changes
|
||||
for (ProjTwin& tw : projTwins) if (tw.id == id) { tw.dirty = true; break; }
|
||||
for (ProjTwin& tw : projTwins) if (tw.id == id) { tw.dirty = true; tw.synced = false; break; }
|
||||
if (prevEnabled2) prevEnabled2(id);
|
||||
};
|
||||
rt->on_killed = [&, prevKilledX = rt->on_killed](int id) { // ExplosionComponent ON_DEATH: the entity explodes as it dies
|
||||
@@ -2657,18 +2704,26 @@ int main(int argc, char** argv) {
|
||||
patchEntity(re->gpu, offsetof(ng::EntityState, st) + 12, &flags, 4);
|
||||
};
|
||||
rt->on_value_changed = [&](int id, const std::string& type, const std::string& key) {
|
||||
if (type == "VariableStorageComponent" || key.rfind("_ng_", 0) == 0) return;
|
||||
ng::RtEntity* re = rt->ent(id);
|
||||
if (type == "Transform") { // a script moved a twin (chain_bolt.lua jumps to the next target): the GPU projectile goes there
|
||||
for (ProjTwin& tw : projTwins) if (tw.id == id && re) {
|
||||
if (ProjTwin* found = findTwin(id); found && re) {
|
||||
ProjTwin& tw = *found;
|
||||
const float* f0 = fprojReadPtr ? reinterpret_cast<const float*>(fprojReadPtr + (size_t)tw.slot * 96) : nullptr;
|
||||
if (f0) if (const ng::ProjDef* pd = gfx.type_def(reinterpret_cast<const int32_t*>(f0 + 12)[0])) { // the jump is part of the path: a trail emitter (is_trail, trail_gap) draws
|
||||
// its particles along it as along any motion (00bc75e0: one per trail_gap of the distance) - chain bolt's purple line between its targets
|
||||
const double ox = f0[0], oy = f0[1], dist = std::hypot(re->x - ox, re->y - oy);
|
||||
const int trailFrame = rt->frame();
|
||||
const double ox = tw.trailFrame == trailFrame ? tw.trailX : f0[0], oy = tw.trailFrame == trailFrame ? tw.trailY : f0[1];
|
||||
const double dist = std::hypot(re->x - ox, re->y - oy);
|
||||
tw.trailFrame = trailFrame; tw.trailX = re->x; tw.trailY = re->y;
|
||||
if (dist > 1.0 && dist < 512.0) for (const ng::EmitterDef& em : pd->fx.emitters) {
|
||||
if (!em.trail || em.trail_gap <= 0.0f || em.real) continue;
|
||||
const int n = std::min(256, (int)(dist / em.trail_gap));
|
||||
// Share the 1024 host requests between live trails. Orbit scripts call both
|
||||
// SetTransform and ApplyTransform; emitting twice starved new fireballs first.
|
||||
const int share = std::max(1, std::min(12, 768 / std::max(1, (int)projTwins.size())));
|
||||
const int n = std::min(share, (int)(dist / em.trail_gap));
|
||||
for (int k = 0; k < n; ++k) {
|
||||
const double t = frand();
|
||||
const double t = (k + frand()) / n;
|
||||
const double vx = em.xv0 + (em.xv1 - em.xv0) * frand(), vy = em.yv0 + (em.yv1 - em.yv0) * frand();
|
||||
emitPart(ox + (re->x - ox) * t, oy + (re->y - oy) * t, vx, vy, em.life0 + (em.life1 - em.life0) * frand(), em.gravity, em.color, 4, 0, em.collide, em.force, em.alpha, em.glow, em.fade);
|
||||
}
|
||||
@@ -2679,14 +2734,16 @@ int main(int argc, char** argv) {
|
||||
// at it (proj_step.comp draw_sprite; -scale in e.x, which only the thrown bodies use)
|
||||
const float ns = -(float)std::max(0.01, std::fabs(re->sx)); queueUp(g.fxState, 16 + (VkDeviceSize)tw.slot * 96 + 64, &ns, 4);
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (type == "DamageModelComponent" && key == "kill_now") { // DamageModelComponent kill_now on a twin (vacuum_powder.lua): the entity dies, its projectile with it
|
||||
for (ProjTwin& tw : projTwins) if (tw.id == id) { killQueue.push_back(id); return; }
|
||||
if (findTwin(id)) { killQueue.push_back(id); return; }
|
||||
}
|
||||
for (ProjTwin& tw : projTwins) if (tw.id == id) { // a script changed a projectile's entity (black_hole_gravity.lua the velocity, SPELLS_TO_POWER the collisions and lifetime...): the GPU projectile follows
|
||||
if (ProjTwin* found = findTwin(id)) { // a script changed a projectile's entity: the GPU projectile follows
|
||||
ProjTwin& tw = *found;
|
||||
const int fld = twinLiveField(type, key);
|
||||
if (fld != -2) tw.synced = false;
|
||||
if (fld == -1) tw.dirty = true; // a field of the type: a new type after the scripts of this frame
|
||||
else if (fld >= 0) { const auto cs = rt->components(id, type, "", true); if (!cs.empty()) twinToGpu(tw.slot, fld, rt->comp(cs.front())); }
|
||||
return;
|
||||
@@ -2773,8 +2830,11 @@ int main(int argc, char** argv) {
|
||||
// an entity without a ProjectileComponent stays the entity the script loaded (wand_pickup.lua / orb_pickup.lua shoot the image emitter wand_effect.xml: as a GPU
|
||||
// projectile it sprayed sparks from one point; the host's emitters draw its image)
|
||||
if (rt->components(entityId, "ProjectileComponent", "", true).empty()) return;
|
||||
pendingShots.push_back({pe->file, shooter, x, y, tx, ty, send});
|
||||
rt->kill(entityId);
|
||||
deferredProjectileSlots.erase(entityId);
|
||||
pendingShots.push_back({pe->file, shooter, x, y, tx, ty, send, entityId});
|
||||
// GameShootProjectile keeps the EntityLoad ID: orbit scripts attach and move it after this call.
|
||||
if (pe->gpu >= 0) { rigid.remove_slot(pe->gpu); hideSlot(pe->gpu); slotToId[pe->gpu] = 0; }
|
||||
if (auto* retained = rt->ent(entityId)) retained->gpu = -1;
|
||||
};
|
||||
if (areaStore) rt->biome_fn = [&](double x, double y) -> std::string { // BiomeMapGetName: the biome of the tile (the map origin is tile (35, 14) at world 0,0)
|
||||
const ng::BiomeDef* bd = areaStore->biome_map().biome_at((int)std::floor(x / 512.0) + 35, (int)std::floor(y / 512.0) + 14);
|
||||
@@ -2791,6 +2851,12 @@ int main(int argc, char** argv) {
|
||||
if (std::getenv("NG_KILLTRACE")) { ng::RtEntity* dbg = rt->ent(id); std::fprintf(stderr, "on_spawned %d %s%c", id, dbg ? dbg->file.c_str() : "?", 10); }
|
||||
ng::RtEntity* re = rt->ent(id);
|
||||
if (!re) return;
|
||||
// EntityLoad followed by GameShootProjectile needs only the projectile slot.
|
||||
// Wait until script shots are collected before allocating an ordinary sprite/body slot.
|
||||
if (!realizingProjectileSlots && re->gpu < 0 && !rt->components(id, "ProjectileComponent", "", true).empty()) {
|
||||
deferredProjectileSlots.insert(id);
|
||||
return;
|
||||
}
|
||||
for (int sc : rt->components(id, "SpriteComponent", "", true)) {
|
||||
auto& sa = rt->comp(sc)->attrs;
|
||||
if (sa["additive"] == "1" && sa["kill_entity_after_finished"] == "1") { pendingFlares.push_back(id); break; }
|
||||
@@ -5531,6 +5597,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
};
|
||||
auto twinSystems = [&](ProjTwin& tw, ng::RtEntity* te) { // the components of a projectile twin the GPU does not simulate, one frame
|
||||
if (tw.hostRevision != te->component_revision) {
|
||||
static const std::set<std::string> hostTypes = {"ExplosionComponent", "MaterialSuckerComponent", "BlackHoleComponent", "MaterialSeaSpawnerComponent", "LightningComponent", "LaserEmitterComponent", "AreaDamageComponent", "GameAreaEffectComponent", "ArcComponent", "CellEaterComponent", "MagicConvertMaterialComponent"};
|
||||
tw.hasHostSystems = false;
|
||||
for (const auto& [type, ids] : te->comps_by_type) if (hostTypes.count(type)) { tw.hasHostSystems = true; break; }
|
||||
tw.hostRevision = te->component_revision;
|
||||
}
|
||||
if (!tw.hasHostSystems) return;
|
||||
for (int xc : rt->components(tw.id, "ExplosionComponent", "", true)) { // ExplosionComponent ON_TIMER: timeout_frames + random(0, timeout_frames_random) after it was made, the explosion (00ae78e0)
|
||||
auto& at = rt->comp(xc)->attrs;
|
||||
if (at["trigger"] != "ON_TIMER") continue;
|
||||
@@ -7242,17 +7315,25 @@ int main(int argc, char** argv) {
|
||||
std::printf("GPU Lua complete frame %u slot %d serial %u fault %u gravity %.7g position %.3f %.3f%c", q[1], k, q[0], q[3], p[6] * 3600.0f, p[0], p[1], 10);
|
||||
}
|
||||
if (rt && psim) { // the twins of the GPU projectiles: their position follows the projectile (the scripts read it), a dead projectile ends its twin; then the components the GPU does not simulate
|
||||
for (size_t ti = 0; ti < projTwins.size();) {
|
||||
PERFMARK("projectile twin sync");
|
||||
bool removeTwins = false;
|
||||
for (size_t ti = 0; ti < projTwins.size(); ++ti) {
|
||||
ProjTwin& tw = projTwins[ti];
|
||||
const float* f = reinterpret_cast<const float*>(fprojReadPtr + (size_t)tw.slot * 96);
|
||||
const int32_t* d = reinterpret_cast<const int32_t*>(f + 12);
|
||||
ng::RtEntity* te = rt->ent(tw.id);
|
||||
if (!te || !te->alive || d[3] != tw.serial) { if (te && te->alive) { twinSystems(tw, te); rt->kill(tw.id); } projTwins.erase(projTwins.begin() + ti); continue; } // (the components run on the death frame too: the engine updates them before the entity goes)
|
||||
if (!te || !te->alive || d[3] != tw.serial) { if (te && te->alive) { twinSystems(tw, te); rt->kill(tw.id); } tw.pendingRemoval = true; removeTwins = true; continue; } // components run on the death frame too
|
||||
te->x = f[0]; te->y = f[1];
|
||||
twinFromGpu(tw.id, f);
|
||||
if (te->gpu > 0) {
|
||||
entReadPtr[te->gpu].pos[0] = f[0]; entReadPtr[te->gpu].pos[1] = f[1];
|
||||
patchEntity(te->gpu, offsetof(ng::EntityState, pos), f, 8);
|
||||
}
|
||||
twinFromGpu(tw.id, f, &tw);
|
||||
PERFMARK("projectile host components");
|
||||
twinSystems(tw, te);
|
||||
++ti;
|
||||
PERFMARK("projectile twin sync");
|
||||
}
|
||||
if (removeTwins) { std::erase_if(projTwins, [](const ProjTwin& tw) { return tw.pendingRemoval; }); ++twinRevision; }
|
||||
}
|
||||
if (rt && cellsReadPtr && cellsViewX == viewX && cellsViewY == viewY) {
|
||||
PERFMARK("loose ground");
|
||||
@@ -9334,9 +9415,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
if (rt) { // the AreaDamageComponents of the other entities (not projectiles) in the loaded area
|
||||
std::set<int> twinIds; for (const ProjTwin& tw : projTwins) twinIds.insert(tw.id);
|
||||
for (int aid : rt->with_component("AreaDamageComponent")) {
|
||||
if (twinIds.count(aid)) continue;
|
||||
if (findTwin(aid)) continue;
|
||||
ng::RtEntity* ae = rt->ent(aid);
|
||||
if (!ae || !ae->alive || std::fabs(ae->x - (viewX + kGridW * 0.5)) > kGridW || std::fabs(ae->y - (viewY + kGridH * 0.5)) > kGridH) continue;
|
||||
areaDamageOf(aid, ae);
|
||||
@@ -9824,10 +9904,9 @@ int main(int argc, char** argv) {
|
||||
emitFrom(rt->player(), rt->player());
|
||||
}
|
||||
{ // a projectile entity a script loaded (EntityLoad, not shot: lantern_death.xml's sparks) has no GPU projectile: its ProjectileComponent lifetime ends it here
|
||||
std::set<int> twinIds; for (const ProjTwin& tw : projTwins) twinIds.insert(tw.id);
|
||||
for (int eid : rt->with_component("ProjectileComponent")) {
|
||||
const ng::RtEntity* pe = rt->ent(eid);
|
||||
if (!pe || !pe->alive || twinIds.count(eid)) continue;
|
||||
if (!pe || !pe->alive || findTwin(eid)) continue;
|
||||
const bool body = pe->gpu > 0 && pe->gpu < entN && entReadPtr && entReadPtr[pe->gpu].st[0] == 4; // (a thrown physics projectile a script loaded: tnt.xml, a slot of its own)
|
||||
if (pe->gpu >= 0 && !body) continue;
|
||||
const auto pcs = rt->components(eid, "ProjectileComponent", "", true);
|
||||
@@ -9851,6 +9930,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
{ // the emitters of the entities without a GPU slot (spawned by scripts: workshop_areadamage.xml's red sparks...) near the camera; children of an entity follow it above
|
||||
const auto imageOwners = rt->with_component_field("ParticleEmitterComponent", "image_animation_file");
|
||||
for (int eid : rt->with_component("ParticleEmitterComponent")) {
|
||||
const ng::RtEntity* ee = rt->ent(eid);
|
||||
if (!ee || !ee->alive || eid == rt->player() || seenEff.count(eid)) continue; // (every entity in the world, those with a GPU slot too: portals, creatures, props, items)
|
||||
@@ -9859,7 +9939,8 @@ int main(int argc, char** argv) {
|
||||
if (rt->alive(ee->parent)) emitFrom(ee->parent, eid);
|
||||
continue;
|
||||
}
|
||||
bool twin = false; for (const ProjTwin& tw : projTwins) if (tw.id == eid) { twin = true; break; }
|
||||
const bool twin = findTwin(eid) != nullptr;
|
||||
if (twin && !std::binary_search(imageOwners.begin(), imageOwners.end(), eid)) continue;
|
||||
emitFrom(eid, eid, twin); // (a twin: only its image_animation_file emitters - SUMMON_WANDGHOST's wand_effect.xml; effects.cpp leaves them to the host)
|
||||
}
|
||||
}
|
||||
@@ -9868,13 +9949,46 @@ int main(int argc, char** argv) {
|
||||
for (int kid : killQueue) rt->kill(kid);
|
||||
killQueue.clear();
|
||||
for (const DeferredBoom& b : hostExplodeDeferred) hostExplode(b.cfg, b.x, b.y, b.who);
|
||||
PERFMARK("energy shields");
|
||||
if (rt && psim) for (int sid : rt->with_component("EnergyShieldComponent")) { // 00adefd0: the shield recharges (recharge_speed per second up to max_energy) and works from energy_required_to_shield;
|
||||
// a projectile of another herd (with damage) that crossed into the ring of radius inside the sector is reflected about the ring (random turn +-0.04 rad), put on the
|
||||
// ring, taken over by the shield's owner (shooter, herd), and costs damage_multiplier x its damage of energy
|
||||
const auto* shieldEntity = rt->ent(sid);
|
||||
const bool noBody = shieldEntity && !shieldEntity->parent && shieldEntity->gpu < 0 && sid != rt->player();
|
||||
if (auto* twin = findTwin(sid)) {
|
||||
if (twin->shieldLayoutRevision != shieldEntity->component_revision) {
|
||||
const auto shields = rt->components(sid, "EnergyShieldComponent", "", true);
|
||||
twin->shieldComponent = shields.size() == 1 ? shields.front() : 0;
|
||||
twin->shieldLayoutRevision = shieldEntity->component_revision;
|
||||
twin->shieldValueRevision = uint64_t(-1);
|
||||
}
|
||||
if (noBody && twin->shieldComponent) {
|
||||
const auto* shield = rt->comp(twin->shieldComponent);
|
||||
if (shield && !shield->enabled) continue;
|
||||
if (shield && twin->shieldValueRevision != shield->value_revision) {
|
||||
const auto number = [&](const char* key, double fallback) { const auto at = shield->attrs.find(key); return at == shield->attrs.end() || at->second.empty() ? fallback : std::atof(at->second.c_str()); };
|
||||
twin->shieldNoOp = number("energy", 0) == number("max_energy", 1) && number("recharge_speed", 1) >= 0;
|
||||
twin->shieldValueRevision = shield->value_revision;
|
||||
}
|
||||
if (shield && twin->shieldNoOp) continue;
|
||||
}
|
||||
// Host recharge/reflection can change energy without a script setter.
|
||||
twin->shieldValueRevision = uint64_t(-1);
|
||||
}
|
||||
for (int ec : rt->components(sid, "EnergyShieldComponent", "", false)) {
|
||||
if (!rt->component_enabled(ec)) continue;
|
||||
auto& at = rt->comp(ec)->attrs;
|
||||
auto num = [&](const char* k, double dv) { auto it = at.find(k); return it == at.end() || it->second.empty() ? dv : std::atof(it->second.c_str()); };
|
||||
auto num = [&](const char* k, double dv) {
|
||||
const auto it = at.find(k);
|
||||
if (it == at.end() || it->second.empty()) return dv;
|
||||
double value;
|
||||
const auto parsed = std::from_chars(it->second.data(), it->second.data() + it->second.size(), value);
|
||||
return parsed.ec == std::errc() && parsed.ptr == it->second.data() + it->second.size() ? value : std::atof(it->second.c_str());
|
||||
};
|
||||
if (noBody) {
|
||||
const auto energy = at.find("energy"), maximum = at.find("max_energy");
|
||||
if (energy != at.end() && maximum != at.end() && !energy->second.empty() && energy->second == maximum->second && num("recharge_speed", 1) >= 0) continue;
|
||||
}
|
||||
double energy = std::min(num("max_energy", 1), num("energy", 0) + num("recharge_speed", 1) / 60.0);
|
||||
const bool active = energy >= num("energy_required_to_shield", 0.2);
|
||||
int root = sid; while (rt->parent(root)) root = rt->parent(root);
|
||||
@@ -9913,10 +10027,11 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
at["mPrevPosition.x"] = std::to_string(cx); at["mPrevPosition.y"] = std::to_string(cy);
|
||||
}
|
||||
at["energy"] = std::to_string(energy);
|
||||
if (energy != num("energy", 0)) at["energy"] = std::to_string(energy);
|
||||
}
|
||||
}
|
||||
hostExplodeDeferred.clear();
|
||||
PERFMARK("explosion flares");
|
||||
// the explosion flares (explosion_flare*.xml: SpriteComponent additive, alpha 0.2, a soft disc scaled 5-10 times by the Transform, played once): an additive
|
||||
// radial light of the sprite's colour over its animation (FAnim slots 256..511 belong to the host)
|
||||
for (int fid : pendingFlares) {
|
||||
@@ -9950,6 +10065,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
pendingFlares.clear();
|
||||
PERFMARK("fog sources");
|
||||
{ // the fog of war holes (SpriteComponent fog_of_war_hole: "the alpha channel of this texture punctures a hole in the fog of war"): the explosion flares'
|
||||
// holes (scale 15, alpha 0.55), torches, wraiths, the sun items: the alpha of their sprite (its frame of the moment) x the component's alpha, scaled; render.comp: fog x (1 - it)
|
||||
const size_t hb = 1024 + 9 * db.materials().size() + 132 + db.materials().size() + 2 + 8192;
|
||||
@@ -9974,7 +10090,7 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
std::vector<float> holes(1 + 64 * 6, 0.0f);
|
||||
int nh = 0;
|
||||
for (int hid : rt->with_component("SpriteComponent")) {
|
||||
for (int hid : rt->with_component_field("SpriteComponent", "fog_of_war_hole", "1")) {
|
||||
if (nh >= 64) break;
|
||||
const ng::RtEntity* he = rt->ent(hid);
|
||||
if (!he || !he->alive) continue;
|
||||
@@ -10016,6 +10132,7 @@ int main(int argc, char** argv) {
|
||||
queueUp(g.tables, (VkDeviceSize)sb * 4, src.data(), (1 + (size_t)ns * 3) * 4);
|
||||
}
|
||||
if (std::getenv("NG_SCRIPTLOG") && !rt->script_errors().empty()) { for (const std::string& er : rt->script_errors()) std::printf("frame %d: script error: %s%c", frame, er.substr(0, 300).c_str(), 10); rt->clear_script_errors(); }
|
||||
PERFMARK("projectile retypes");
|
||||
if (rt && psim) for (ProjTwin& tw : projTwins) if (tw.dirty) { // the twins whose type fields the scripts changed this frame: the projectile takes the type of the changed copy
|
||||
tw.dirty = false;
|
||||
const std::string sig = twinSig(tw.id);
|
||||
@@ -10076,14 +10193,35 @@ int main(int argc, char** argv) {
|
||||
if (pid2) rt->on_shoot(rt->player(), sx0, sy0, sx0 + tdx, sy0 + tdy, pid2, true);
|
||||
}
|
||||
for (const ScriptShot& ss : pendingShots) { // the shots of scripts: speed from the aim vector (the scripts pass the target as position + velocity, in pixels per second), the shooter is credited with the hits
|
||||
auto it = scriptShotNode.find(ss.file);
|
||||
if (it == scriptShotNode.end() && psim) it = scriptShotNode.emplace(ss.file, gfx.persistent_node(*psim, ss.file)).first;
|
||||
if (it == scriptShotNode.end() || it->second < 0) continue;
|
||||
if (ss.entity && !rt->alive(ss.entity)) continue;
|
||||
std::string file = ss.file;
|
||||
if (ss.entity && psim) {
|
||||
ng::EntityDef ed; ed.path = ss.file;
|
||||
for (const auto& tag : rt->ent(ss.entity)->tags) ed.tags += (ed.tags.empty() ? "" : ",") + tag;
|
||||
for (int cid : rt->ent(ss.entity)->comps) if (const auto* c = rt->comp(cid); c && rt->component_enabled(cid)) {
|
||||
ed.comps.push_back({c->type, c->attrs});
|
||||
}
|
||||
file = ng::ProjectileSim::script_definition_key(ed);
|
||||
psim->def_from(file, ed);
|
||||
}
|
||||
auto it = scriptShotNode.find(file);
|
||||
if (it == scriptShotNode.end() && psim) it = scriptShotNode.emplace(file, gfx.persistent_node(*psim, file)).first;
|
||||
if (it == scriptShotNode.end() || it->second < 0) { if (ss.entity) rt->kill(ss.entity); continue; }
|
||||
int slot = -1;
|
||||
if (ss.shooter == rt->player()) slot = 0;
|
||||
else for (int k = 1; k < entN && slot < 0; ++k) if (slotToId[k] == ss.shooter) slot = k;
|
||||
if (std::getenv("NG_SCRIPTLOG")) std::printf("frame %d: script shot %s by entity %d (slot %d) node %d herd %d%c", frame, ss.file.c_str(), ss.shooter, slot, it->second, slot >= 0 ? entReadPtr[slot].ex8[1] : -1, 10);
|
||||
gfx.shoot(frame, ss.x, ss.y, (ss.tx - ss.x) / 60.0, (ss.ty - ss.y) / 60.0, it->second, ss.shooter == rt->player() ? 1 : 2, slot + 1, slot >= 0 ? entReadPtr[slot].ex8[1] : 0, !ss.send); // (send_message false: as if the script_shot scripts had run)
|
||||
if (!gfx.shoot(frame, ss.x, ss.y, (ss.tx - ss.x) / 60.0, (ss.ty - ss.y) / 60.0, it->second, ss.shooter == rt->player() ? 1 : 2, slot + 1, slot >= 0 ? entReadPtr[slot].ex8[1] : 0, !ss.send, ss.entity) && ss.entity) rt->kill(ss.entity);
|
||||
}
|
||||
if (!deferredProjectileSlots.empty()) {
|
||||
const auto deferred = std::move(deferredProjectileSlots);
|
||||
deferredProjectileSlots.clear();
|
||||
realizingProjectileSlots = true;
|
||||
const bool wasRespawning = respawning;
|
||||
respawning = true; // the initial notification already played the spawn sound
|
||||
for (int id : deferred) if (const auto* e = rt->ent(id); e && e->alive && e->gpu < 0) rt->on_spawned(id);
|
||||
respawning = wasRespawning;
|
||||
realizingProjectileSlots = false;
|
||||
}
|
||||
if (const char* mb = std::getenv("NG_MATBOX")) if (cellsReadPtr) { // test NG_MATBOX=x0:y0:x1:y1:every - the materials of a box of world cells (all but air)
|
||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0, ev = 30;
|
||||
@@ -10215,7 +10353,10 @@ int main(int argc, char** argv) {
|
||||
if (activeItem < 0 && gun->equipped()) for (int k = 0; k < ng::Inventory::kWands; ++k) if (inv.wands[k] == gun->equipped()) activeWand = k; // the wand in the hand stays in the hand when it is moved to another slot
|
||||
for (int& w : inv.items) if (w && (!rt->ent(w) || !rt->ent(w)->alive)) w = 0;
|
||||
for (int& w : inv.full) if (w && (!rt->ent(w) || !rt->ent(w)->alive)) w = 0;
|
||||
if (!inv.wands[activeWand]) for (int k = 0; k < ng::Inventory::kWands; ++k) if (inv.wands[k]) { activeWand = k; gun->equip(inv.wands[k]); break; }
|
||||
if (!inv.wands[activeWand]) {
|
||||
for (int k = 0; k < ng::Inventory::kWands; ++k) if (inv.wands[k]) { activeWand = k; break; }
|
||||
}
|
||||
if (gun->equipped() != inv.wands[activeWand]) gun->equip(inv.wands[activeWand]);
|
||||
const bool selNew = g_in.select_new; g_in.select_new = false;
|
||||
const int wheelSteps = g_in.wheel; g_in.wheel = 0;
|
||||
const int selBefore = activeItem >= 0 ? 4 + activeItem : activeWand;
|
||||
@@ -10590,7 +10731,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wantFire && playMode && aimXc >= 0 && gun->equipped() && activeItem < 0) {
|
||||
if (wantFire && playMode && aimXc >= 0 && gun->equipped() && gun->equipped() == inv.wands[activeWand] && activeItem < 0) {
|
||||
ng::ShotResult shot;
|
||||
if (gun->fire(frame, shot)) {
|
||||
++shotsFired;
|
||||
@@ -11244,7 +11385,7 @@ int main(int argc, char** argv) {
|
||||
const int uiL = std::max(1, (int)std::lround(curW * 2.8 / 2000.0)), tsL = std::max(1, uiL * 2 / 3);
|
||||
PERFMARK("text sprites, panels");
|
||||
{ // the text sprites of the world (SpriteComponent is_text_sprite: the shop prices in font_pixel_white), at the entity minus the sprite offset, one font pixel per world pixel
|
||||
for (int tid : rt->with_component("SpriteComponent")) {
|
||||
for (int tid : rt->with_component_field("SpriteComponent", "is_text_sprite", "1")) {
|
||||
const ng::RtEntity* te = rt->ent(tid);
|
||||
if (!te || !te->alive || te->parent) continue;
|
||||
for (int cc : rt->components(tid, "ItemCostComponent", "", true)) { // ItemCostSystem (00b8f9f0): a changed cost is written into the text of the sprite tagged shop_cost
|
||||
@@ -11686,7 +11827,7 @@ int main(int argc, char** argv) {
|
||||
lastFrameT = nowT;
|
||||
if (frame > 5 && std::getenv("NG_FRAMESTAT") && dt > 40.0) { std::printf("slow frame %d: %.1f ms (region step %.1f ms, spawns %.1f ms, gpu %.1f ms)", frame, dt, lastPollMs, lastSpawnMs, lastGpuRaw); if (false) { auto d = [&](int x, int y) { return std::chrono::duration<double, std::milli>(cpuT[y] - cpuT[x]).count(); }; std::printf(" (input+camera %.1f, game logic %.1f, HUD %.1f)", d(0, 1), d(1, 2), d(2, 3)); } std::putchar(10); }
|
||||
frameMs = frameMs == 0 ? dt : frameMs * 0.9 + dt * 0.1;
|
||||
if (std::getenv("NG_PERFLOG") && frame % 60 == 0) { int lp = 0; if (fprojReadPtr) for (int k = 0; k < ng::GpuFx::kMaxProj; ++k) lp += reinterpret_cast<const int32_t*>(fprojReadPtr + (size_t)k * 96 + 48)[3] != 0; std::printf("perf frame %d: %.1f ms per frame, gpu %.1f ms, entities %d, runtime %d, projectiles %d%c", frame, frameMs, gpuMs, entN, rt ? (int)rt->with_tag("").size() : 0, lp, 10); } // test: the frame time over a run
|
||||
if (std::getenv("NG_PERFLOG") && frame % 60 == 0) { int lp = 0; if (fprojReadPtr) for (int k = 0; k < ng::GpuFx::kMaxProj; ++k) lp += reinterpret_cast<const int32_t*>(fprojReadPtr + (size_t)k * 96 + 48)[3] != 0; std::printf("perf frame %d: %.1f ms per frame, gpu %.1f ms, entities %d, runtime %d, projectiles %d, native scripts %ld%c", frame, frameMs, gpuMs, entN, rt ? (int)rt->with_tag("").size() : 0, lp, rt ? rt->native_script_runs() : 0, 10); } // test: the frame time over a run
|
||||
}
|
||||
hs.gpu_ms = gpuMs; hs.frame_ms = frameMs; hs.sim_speed = simSpeed; hs.show_timing = !worldMode;
|
||||
hs.fps = lastFps; hs.entities = entN; hs.stored_tiles = (int)store.tiles(); hs.frame = frame; hs.seed_text = seedText.c_str();
|
||||
@@ -12153,8 +12294,13 @@ int main(int argc, char** argv) {
|
||||
if (std::getenv("NG_SCRIPTLOG")) std::printf("frame %d: cast creature %s -> entity %d at (%.0f, %.0f)%c", frame, pd->file.c_str(), cid, e[0], e[1], 10);
|
||||
continue;
|
||||
}
|
||||
const int twin = spawnTwin(pd, e[0], e[1]);
|
||||
if (!twin) continue;
|
||||
const int retained = e2[3] < 0.0f ? -(int)e2[3] : 0;
|
||||
const int twin = retained ? (rt->alive(retained) ? retained : 0) : spawnTwin(pd, e[0], e[1]);
|
||||
if (!twin) {
|
||||
// Its parent may have died while the spawn event was in flight.
|
||||
if (retained) { const int32_t* state = reinterpret_cast<const int32_t*>(fprojReadPtr + (size_t)e[3] * 96 + 48); if (state[3] == (int)e2[0]) { const int32_t flags = state[2] | (1 << 29); queueUp(g.fxState, 16 + (VkDeviceSize)e[3] * 96 + 56, &flags, 4); } }
|
||||
continue;
|
||||
}
|
||||
const int sslot = (int)e2[2];
|
||||
const int shooterId = sslot == 1 ? rt->player() : (sslot > 1 && sslot <= entN ? slotToId[sslot - 1] : 0);
|
||||
for (int pcid : rt->components(twin, "ProjectileComponent", "", true)) rt->comp(pcid)->attrs["mWhoShot"] = std::to_string(shooterId);
|
||||
@@ -12169,6 +12315,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
castTwins[shooterId].push_back(twin); // (the projectiles of one cast: their arcs are linked below) // (the type's fields as the file has them: what the on-added scripts change makes a new type)
|
||||
projTwins.push_back(tw);
|
||||
++twinRevision;
|
||||
addProjectileDamageProxy(twin, tw.slot);
|
||||
if (std::getenv("NG_SCRIPTLOG")) std::printf("frame %d: twin %d of projectile slot %d serial %d (%s)%c", frame, twin, tw.slot, tw.serial, pd->file.c_str(), 10);
|
||||
if (ng::RtEntity* te0 = rt->ent(twin)) twinSystems(projTwins.back(), te0); // the spawn frame counts (the engine updates the components the frame the projectile is made)
|
||||
continue;
|
||||
@@ -12214,7 +12362,7 @@ int main(int argc, char** argv) {
|
||||
if (e[2] == -5.0f && k + 1 < std::min(head, 512)) { // the projectile of a twin died
|
||||
const float* e2 = xe + 4 * (k + 1);
|
||||
++k;
|
||||
for (size_t ti = 0; ti < projTwins.size(); ++ti) if (projTwins[ti].slot == (int)e[3] && projTwins[ti].serial == (int)e2[0]) { if (ng::RtEntity* td = rt->ent(projTwins[ti].id)) { td->x = e[0]; td->y = e[1]; } rt->kill(projTwins[ti].id); projTwins.erase(projTwins.begin() + ti); break; }
|
||||
for (size_t ti = 0; ti < projTwins.size(); ++ti) if (projTwins[ti].slot == (int)e[3] && projTwins[ti].serial == (int)e2[0]) { if (ng::RtEntity* td = rt->ent(projTwins[ti].id)) { td->x = e[0]; td->y = e[1]; } rt->kill(projTwins[ti].id); projTwins.erase(projTwins.begin() + ti); ++twinRevision; break; }
|
||||
continue;
|
||||
}
|
||||
if (e[2] == -3.0f && k + 1 < std::min(head, 512)) { // Message_Shot: a shot of a shooter with script_shot scripts (two entries: position / type, then angle, speed, base angle, shooter slot + 1 | team << 20)
|
||||
@@ -12909,12 +13057,32 @@ int main(int argc, char** argv) {
|
||||
uint8_t* mp = nullptr;
|
||||
VK(vkMapMemory(g.dev, g.stagingFrame.mem, 0, VK_WHOLE_SIZE, 0, (void**)&mp));
|
||||
size_t used = 0;
|
||||
std::vector<VkBufferCopy> copies;
|
||||
copies.reserve(queuedUploads.size());
|
||||
const Buffer* batchDestination = nullptr;
|
||||
VkDeviceSize previousEnd = 0;
|
||||
std::set<const Buffer*> writtenBuffers;
|
||||
auto flushCopies = [&] {
|
||||
if (copies.empty()) return;
|
||||
if (writtenBuffers.count(batchDestination)) {
|
||||
const VkMemoryBarrier ordered{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT};
|
||||
vkCmdPipelineBarrier(g.cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &ordered, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
vkCmdCopyBuffer(g.cmd, g.stagingFrame.buf, batchDestination->buf, (uint32_t)copies.size(), copies.data());
|
||||
writtenBuffers.insert(batchDestination);
|
||||
copies.clear();
|
||||
};
|
||||
for (const QueuedUpload& q : queuedUploads) {
|
||||
std::memcpy(mp + used, q.data.data(), q.data.size());
|
||||
const VkBufferCopy bc{used, q.off, q.data.size()};
|
||||
vkCmdCopyBuffer(g.cmd, g.stagingFrame.buf, q.dst->buf, 1, &bc); // (in order: a later piece over the same bytes wins, as before)
|
||||
// Consecutive, non-overlapping regions can share one Vulkan command.
|
||||
// A destination change or backwards patch ends the batch, retaining order.
|
||||
if (q.dst != batchDestination || q.off < previousEnd) flushCopies();
|
||||
batchDestination = q.dst;
|
||||
copies.push_back({used, q.off, q.data.size()});
|
||||
previousEnd = q.off + q.data.size();
|
||||
used += q.data.size();
|
||||
}
|
||||
flushCopies();
|
||||
vkUnmapMemory(g.dev, g.stagingFrame.mem);
|
||||
queuedUploads.clear();
|
||||
const VkMemoryBarrier ub{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT};
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ namespace ng {
|
||||
|
||||
class PerfRecorder {
|
||||
public:
|
||||
static constexpr int kMaxSections = 32, kGpu = 20, kCounts = 12;
|
||||
static constexpr int kMaxSections = 64, kGpu = 20, kCounts = 12;
|
||||
struct Frame {
|
||||
int frame = 0;
|
||||
float total = 0, wait = 0, gpu = 0; // whole frame (wall time from one frame to the next), waiting for the GPU / the swap chain, the GPU's own time
|
||||
|
||||
Reference in new issue
Block a user