experiment: LivingPlane repeatable + standalone UE runtime module (L164)
Readback barrier fix (GL_TEXTURE_UPDATE_BARRIER_BIT before glGetTexImage); check_repeatability.py compares full-field checksums across query/capture settings - Intel GPU PASS (3b9d45905c7aafff), llvmpipe PASS, mismatch control fails. New runtime module VoxelForgeLivingPlane (separate from generation): RDG global shader (Shaders/LivingPlane.usf), ping-pong RGBA32F render targets, actor with WPO plane and object inputs, no CPU readback. Compiles in Linux UE 5.7. Windows build: Source/VoxelForgeLivingPlane/*, Shaders/LivingPlane.usf, VoxelForge.uplugin. Setup: UE-SETUP.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
e1d87eed26
commit
3b71d35eba
13 files changed
+868
-8
No files matched your search
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
@@ -48,7 +50,7 @@ namespace
|
||||
struct FOptions
|
||||
{
|
||||
std::string Device="auto";
|
||||
std::filesystem::path Output="Saved/Linux/l163/living-plane";
|
||||
std::filesystem::path Output="Saved/Linux/l164/living-plane";
|
||||
std::string Scene="wake";
|
||||
int Size=128;
|
||||
int Steps=360;
|
||||
@@ -1011,12 +1013,29 @@ void main()
|
||||
return Values[std::min(I,Values.size()-1)];
|
||||
}
|
||||
|
||||
std::uint64_t FieldChecksum(const std::vector<float>& Field)
|
||||
{
|
||||
// Hash float bit patterns in canonical little-endian byte order so the
|
||||
// result does not depend on the host's native byte order.
|
||||
std::uint64_t Hash=14695981039346656037ull;
|
||||
for(const float Value:Field)
|
||||
{
|
||||
const std::uint32_t Bits=std::bit_cast<std::uint32_t>(Value);
|
||||
for(unsigned Byte=0;Byte<4;++Byte)
|
||||
{
|
||||
Hash^=static_cast<std::uint8_t>((Bits>>(Byte*8))&0xffu);
|
||||
Hash*=1099511628211ull;
|
||||
}
|
||||
}
|
||||
return Hash;
|
||||
}
|
||||
|
||||
bool WriteTimingJson(const FOptions& Options,const FContext& Context,
|
||||
std::string_view Renderer,std::string_view Vendor,std::string_view Version,
|
||||
double Dt,const std::vector<GLuint64>& Times,const std::vector<double>& Heights,
|
||||
const std::vector<double>& Velocities,std::size_t Frames,int TimerQueryBits,
|
||||
int TimerQueryWaits,bool TimerResultsAvailable,double DispatchWallMsPerStep,
|
||||
bool FirstStepAudit,bool DynamicsAudit)
|
||||
std::uint64_t FieldChecksumValue,bool FirstStepAudit,bool DynamicsAudit)
|
||||
{
|
||||
std::error_code Error;
|
||||
std::filesystem::create_directories(Options.Output,Error);
|
||||
@@ -1035,6 +1054,9 @@ void main()
|
||||
double HeightSquare=0.0;
|
||||
for(const double H:Heights)HeightSquare+=H*H;
|
||||
const double HeightRms=std::sqrt(HeightSquare/Heights.size());
|
||||
char FieldChecksumHex[17]{};
|
||||
std::snprintf(FieldChecksumHex,sizeof(FieldChecksumHex),"%016llx",
|
||||
static_cast<unsigned long long>(FieldChecksumValue));
|
||||
const double Dx=PlaneLengthMeters/Options.Size;
|
||||
const double SpectralStep=Dt*std::sqrt(8.0*WaveSpeedMetersPerSecond*
|
||||
WaveSpeedMetersPerSecond/(Dx*Dx)+FoundationStiffnessPerSecondSquared);
|
||||
@@ -1087,6 +1109,7 @@ void main()
|
||||
<<" \"field_max_m\": "<<HeightMax<<",\n"
|
||||
<<" \"field_mean_m\": "<<HeightMean<<",\n"
|
||||
<<" \"field_rms_m\": "<<HeightRms<<",\n"
|
||||
<<" \"field_checksum_fnv1a64\": \""<<FieldChecksumHex<<"\",\n"
|
||||
<<" \"max_abs_velocity_m_per_s\": "<<std::abs(VelocityMax)<<",\n"
|
||||
<<" \"gl_time_elapsed_ms_per_step\": [";
|
||||
for(std::size_t I=0;I<Times.size();++I)
|
||||
@@ -1284,6 +1307,10 @@ void main()
|
||||
glFinish();
|
||||
FirstField.resize(static_cast<std::size_t>(Size)*Size*4);
|
||||
glBindTexture(GL_TEXTURE_2D,Fields[static_cast<std::size_t>(Current)]);
|
||||
// glGetTexImage is a texture-update access. The image/fetch
|
||||
// barriers used for simulation and rendering do not cover this
|
||||
// readback visibility path.
|
||||
glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
|
||||
glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_FLOAT,FirstField.data());
|
||||
const int X=std::clamp(static_cast<int>(std::floor(
|
||||
(Bodies[0].X+PlaneLengthMeters*0.5)/Dx)),0,Size-1);
|
||||
@@ -1329,6 +1356,7 @@ void main()
|
||||
|
||||
std::vector<float> FinalField(static_cast<std::size_t>(Size)*Size*4);
|
||||
glBindTexture(GL_TEXTURE_2D,Fields[static_cast<std::size_t>(Current)]);
|
||||
glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
|
||||
glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_FLOAT,FinalField.data());
|
||||
std::vector<double> Heights,Velocities;
|
||||
Heights.reserve(static_cast<std::size_t>(Size)*Size);
|
||||
@@ -1346,6 +1374,7 @@ void main()
|
||||
const auto [HeightMinIt,HeightMaxIt]=std::minmax_element(Heights.begin(),Heights.end());
|
||||
const bool DynamicsAudit=Finite&&*HeightMinIt<-1.0e-7&&*HeightMaxIt-*HeightMinIt<2.0
|
||||
&&(!Options.Imprint||ImprintMin>=-Options.ImprintMaxDepthMeters-1.0e-5);
|
||||
const std::uint64_t FinalFieldChecksum=FieldChecksum(FinalField);
|
||||
const bool Good=FirstStepAudit&&DynamicsAudit&&QueryAudit&&glGetError()==GL_NO_ERROR;
|
||||
if(!DynamicsAudit)
|
||||
std::cerr<<"[LivingPlane] field audit failed: finite="<<Finite<<" min="
|
||||
@@ -1354,7 +1383,7 @@ void main()
|
||||
std::cerr<<"[LivingPlane] timer-query result availability audit failed after glFinish\n";
|
||||
if(!WriteTimingJson(Options,Context,Renderer,Vendor,Version,Dt,Times,Heights,Velocities,
|
||||
FramesWritten,TimerQueryBits,TimerQueryWaits,TimerResultsAvailable,
|
||||
DispatchWallMsPerStep,FirstStepAudit,DynamicsAudit))
|
||||
DispatchWallMsPerStep,FinalFieldChecksum,FirstStepAudit,DynamicsAudit))
|
||||
{Render.Destroy();if(QueryCount>0)glDeleteQueries(QueryCount,Queries.data());
|
||||
glDeleteTextures(2,Fields.data());glDeleteProgram(ComputeProgram);return false;}
|
||||
|
||||
@@ -1375,6 +1404,8 @@ void main()
|
||||
<<" timer_query_bits="<<TimerQueryBits<<" timer_query_waits="<<TimerQueryWaits;
|
||||
std::cout<<" field_min_m="<<*HeightMinIt<<" field_max_m="<<*HeightMaxIt
|
||||
<<" imprint_min_m="<<ImprintMin<<" frames="<<FramesWritten
|
||||
<<" field_checksum_fnv1a64="<<std::hex<<std::setw(16)<<std::setfill('0')
|
||||
<<FinalFieldChecksum<<std::dec<<std::setfill(' ')
|
||||
<<" equation_audit="<<(FirstStepAudit?"PASS":"FAIL")
|
||||
<<" timer_query_audit="<<(Options.TimerQueries
|
||||
?(QueryAudit?"PASS":"FAIL"):"NOT_RUN")
|
||||
|
||||
Reference in new issue
Block a user