diff --git a/files.cmake b/files.cmake index 3a6e7cf177..71353a19b8 100644 --- a/files.cmake +++ b/files.cmake @@ -1469,6 +1469,7 @@ set(DUSK_FILES src/dusk/interp/line.cpp src/dusk/interp/particle.cpp src/dusk/interp/samples.cpp + src/dusk/interp/vertex.cpp src/dusk/io.cpp src/dusk/iso_validate.cpp src/dusk/language.cpp diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp index 77dc45365b..49771243d2 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp @@ -8,7 +8,12 @@ #include "JSystem/JSupport/JSupport.h" +#if TARGET_PC +#include "dusk/interp/vertex.h" +#endif + J3DDeformData::J3DDeformData() { + IF_DUSK(dusk::interp::vertex::invalidate(this)); mClusterNum = 0; mClusterKeyNum = 0; mClusterVertexNum = 0; @@ -67,6 +72,7 @@ void J3DDeformData::deform(J3DVertexBuffer* buffer) { } void J3DDeformData::setAnm(J3DAnmCluster* anm) { + IF_DUSK(dusk::interp::vertex::invalidate(this)); for (u16 i = 0; i < mClusterNum; i++) { DEFORMER(i)->setAnmCluster(anm); } diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp index 5198cf63c8..7408e399cb 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp @@ -11,6 +11,7 @@ #if TARGET_PC #include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/vertex.h" #endif #define J3D_ASSERTMSG(LINE, COND, MSG) JUT_ASSERT_MSG(LINE, (COND) != 0, MSG) @@ -131,6 +132,7 @@ void J3DModel::setAnmMtx(int jointNo, Mtx m) { } void J3DModel::forgetMtx() { + dusk::interp::vertex::reset(&mVertexBuffer); dusk::interp::forget_mtx(mPresentationBase); for (u16 i = 0; i < mModelData->getJointNum(); ++i) { dusk::interp::forget_mtx(getAnmMtx(i)); @@ -494,7 +496,9 @@ void J3DModel::calc() { mCalcCallBack(this, 0); } -#ifdef TARGET_PC +#if TARGET_PC + dusk::interp::vertex::capture(&mVertexBuffer, mDeformData); + for (u16 i = 0; i < mModelData->getJointNum(); ++i) { dusk::interp::record_final_mtx(getAnmMtx(i)); } diff --git a/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp b/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp index 4c1944478a..6d605e64c6 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DVertex.cpp @@ -6,6 +6,10 @@ #include "JSystem/JKernel/JKRHeap.h" #include +#if TARGET_PC +#include "dusk/interp/vertex.h" +#endif + J3DVertexData::J3DVertexData() { mVtxNum = 0; mNrmNum = 0; @@ -31,6 +35,7 @@ J3DVertexData::J3DVertexData() { } void J3DVertexBuffer::setVertexData(J3DVertexData* pVtxData) { + IF_DUSK(dusk::interp::vertex::reset(this)); J3D_ASSERT_NULLPTR(175, pVtxData != NULL); mVtxData = pVtxData; @@ -50,6 +55,7 @@ void J3DVertexBuffer::setVertexData(J3DVertexData* pVtxData) { } void J3DVertexBuffer::init() { + IF_DUSK(dusk::interp::vertex::reset(this)); mVtxData = NULL; mVtxPosArray[0] = mVtxPosArray[1] = NULL; @@ -65,11 +71,15 @@ void J3DVertexBuffer::init() { frameInit(); } -J3DVertexBuffer::~J3DVertexBuffer() {} +J3DVertexBuffer::~J3DVertexBuffer() { + IF_DUSK(dusk::interp::vertex::reset(this)); +} void J3DVertexBuffer::setArray() const { - j3dSys.setVtxPos(mCurrentVtxPos, mVtxData->getVtxNum()); - j3dSys.setVtxNrm(mCurrentVtxNrm, mVtxData->getNrmNum()); + j3dSys.setVtxPos(DUSK_IF_ELSE(dusk::interp::vertex::positions(this, mCurrentVtxPos), mCurrentVtxPos), + mVtxData->getVtxNum()); + j3dSys.setVtxNrm(DUSK_IF_ELSE(dusk::interp::vertex::normals(this, mCurrentVtxNrm), mCurrentVtxNrm), + mVtxData->getNrmNum()); j3dSys.setVtxCol(mCurrentVtxCol, mVtxData->getColNum()); } diff --git a/src/dusk/interp/frame_interpolation.cpp b/src/dusk/interp/frame_interpolation.cpp index 70aa3eee32..229217281b 100644 --- a/src/dusk/interp/frame_interpolation.cpp +++ b/src/dusk/interp/frame_interpolation.cpp @@ -4,6 +4,7 @@ #include "dusk/interp/lerp.h" #include "dusk/interp/particle.h" #include "dusk/interp/samples.h" +#include "dusk/interp/vertex.h" #include "mtx.h" @@ -106,11 +107,12 @@ void clear_interpolation_history() { s_previousRecording = {}; s_currentRecording = {}; clear_replacements(); - dusk::interp::clear_owned_samples(); - dusk::interp::particle::clear(); - dusk::interp::clear_weather_samples(); clear_callbacks(); dusk::interp::camera_invalidate_snapshots(); + dusk::interp::clear_owned_samples(); + dusk::interp::clear_weather_samples(); + dusk::interp::particle::clear(); + dusk::interp::vertex::clear(); s_presentationDepth = 0; s_cameraPresentationActive = false; } @@ -128,6 +130,7 @@ void begin_sim_tick() { camera_on_sim_tick(); ++s_simTickSeq; particle::prune(); + vertex::prune(); } uint64_t sim_tick_seq() { diff --git a/src/dusk/interp/vertex.cpp b/src/dusk/interp/vertex.cpp new file mode 100644 index 0000000000..790f508484 --- /dev/null +++ b/src/dusk/interp/vertex.cpp @@ -0,0 +1,131 @@ +#include "vertex.h" + +#include "frame_interpolation.h" +#include "dusk/game_clock.h" +#include "JSystem/J3DGraphBase/J3DVertex.h" + +#include +#include +#include + +namespace dusk::interp::vertex { +namespace { + +struct Array { + std::vector previous; + std::vector current; + std::vector presentation; + const void* source = nullptr; + float cached_step = -1.0f; + + void capture(const void* data, u32 count, bool continuous, bool same_tick) { + source = data; + cached_step = -1.0f; + if (data == nullptr || count == 0) { + *this = {}; + return; + } + continuous &= current.size() == count; + if (continuous && !same_tick) previous.swap(current); + const auto* values = static_cast(data); + current.assign(values, values + count); + if (!continuous) previous = current; + } + + void* read(void* requested, float step) { + if (requested != source || current.empty() || step >= 1.0f) return requested; + if (step <= 0.0f) return previous.data(); + if (step != cached_step) { + presentation.resize(current.size()); + for (size_t i = 0; i < current.size(); ++i) { + const auto& a = previous[i]; + const auto& b = current[i]; + presentation[i] = {a.x + (b.x - a.x) * step, + a.y + (b.y - a.y) * step, + a.z + (b.z - a.z) * step}; + } + cached_step = step; + } + return presentation.data(); + } +}; + +struct Record { + const J3DDeformData* deformation = nullptr; + const J3DVertexData* layout = nullptr; + uint64_t tick = 0; + uint64_t epoch = 0; + Array positions; + Array normals; +}; + +auto& records() { + static std::unordered_map stored; + return stored; +} + +void* read(const J3DVertexBuffer* buffer, void* current, Array Record::* array) { + if (!is_enabled() || !is_presentation_active()) return current; + auto it = records().find(buffer); + if (it == records().end() || it->second.tick != sim_tick_seq() || + it->second.epoch != game_clock::g_frameTiming.presentationEpoch) + { + return current; + } + return (it->second.*array).read(current, get_interpolation_step()); +} + +} // namespace + +void capture(J3DVertexBuffer* buffer, const J3DDeformData* deformation) { + if (!should_capture() || is_presentation_active()) return; + if (deformation == nullptr) { + reset(buffer); + return; + } + auto* layout = buffer->getVertexData(); + void* positions = buffer->getCurrentVtxPos(); + void* normals = buffer->getCurrentVtxNrm(); + if (layout->getVtxNum() == 0 || layout->getVtxPosType() != GX_F32 || + positions == layout->getVtxPosArray()) positions = nullptr; + if (layout->getNrmNum() == 0 || layout->getVtxNrmType() != GX_F32 || + normals == layout->getVtxNrmArray()) normals = nullptr; + if (positions == nullptr && normals == nullptr) { + reset(buffer); + return; + } + + auto& record = records()[buffer]; + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + const bool same_tick = record.tick == tick; + const bool continuous = record.deformation == deformation && record.layout == layout && + record.epoch == epoch && (same_tick || record.tick + 1 == tick); + + record.positions.capture(positions, layout->getVtxNum(), continuous, same_tick); + record.normals.capture(normals, layout->getNrmNum(), continuous, same_tick); + record.deformation = deformation; + record.layout = layout; + record.tick = tick; + record.epoch = epoch; +} + +void* positions(const J3DVertexBuffer* buffer, void* current) { return read(buffer, current, &Record::positions); } +void* normals(const J3DVertexBuffer* buffer, void* current) { return read(buffer, current, &Record::normals); } +void reset(const J3DVertexBuffer* buffer) { records().erase(buffer); } + +void invalidate(const J3DDeformData* deformation) { + std::erase_if(records(), [=](const auto& entry) { return entry.second.deformation == deformation; }); +} + +void prune() { + const uint64_t tick = sim_tick_seq(); + const uint64_t epoch = game_clock::g_frameTiming.presentationEpoch; + std::erase_if(records(), [=](const auto& entry) { + return entry.second.epoch != epoch || entry.second.tick < tick - (tick != 0); + }); +} + +void clear() { records().clear(); } + +} // namespace dusk::interp::vertex diff --git a/src/dusk/interp/vertex.h b/src/dusk/interp/vertex.h new file mode 100644 index 0000000000..ce00a84c1c --- /dev/null +++ b/src/dusk/interp/vertex.h @@ -0,0 +1,16 @@ +#pragma once + +class J3DVertexBuffer; +class J3DDeformData; + +namespace dusk::interp::vertex { + +void capture(J3DVertexBuffer* buffer, const J3DDeformData* deformation); +void* positions(const J3DVertexBuffer* buffer, void* current); +void* normals(const J3DVertexBuffer* buffer, void* current); +void reset(const J3DVertexBuffer* buffer); +void invalidate(const J3DDeformData* deformation); +void prune(); +void clear(); + +} // namespace dusk::interp::vertex