wip(assembler): rounds L5+L6 -- Linux far-terrain captures and screen-space gate (world unchanged)

Linux offscreen capture (EGL/GLES pbuffer; llvmpipe in the sandbox) and an opt-in screen-space gate
for LOD>=3 (normal distance projected at each LOD's closest display distance, 1 px at 1440p =
0.61 mrad). The gate rejects the S13 octave-drop and stride-2 controls, fault injections, and a
vertical-vs-screen control. Finding: today's far meshes differ from the field by up to 92 px, and
LOD swaps snap mesa rims by tens of px (p50 0 px), a silhouette effect of plain LOD switching. The
octave cutoff was dropped: nothing qualified, and octaves are a small share of far time. Off-path
cost: reviewer interleaved W12 within noise vs L4 (+0.6% median). Hashes = L4.
Largest far lever measured: column work outside noise, 34% of far worker time (duplicate XY across tiles).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ED33VnqepD4YY6gDAAA832
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 21:10:08 +02:00
1 parent 7770074503
commit 9898e057fd
36 files changed
+1821 -11

No files matched your search

+397
View File
@@ -0,0 +1,397 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <png.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <string_view>
#include <vector>
namespace
{
struct FVertex
{
float Position[3];
float Normal[3];
float Height;
};
struct FScene
{
std::uint32_t VoxelSizeMicrons=0;
std::array<double,3> CameraMeters{};
double YawDegrees=0.0,PitchDegrees=0.0;
std::vector<FVertex> Vertices;
std::vector<std::uint32_t> Indices;
std::uint64_t Triangles=0,SkirtTriangles=0;
std::uint32_t TileCount=0;
};
template<typename T> bool Read(std::ifstream& File,T& Value)
{
File.read(reinterpret_cast<char*>(&Value),sizeof(Value));
return static_cast<bool>(File);
}
bool LoadScene(const std::filesystem::path& Path,FScene& Scene,std::string& Error)
{
std::ifstream File(Path,std::ios::binary);
if(!File){Error="cannot open mesh scene";return false;}
char Magic[8]{};
File.read(Magic,sizeof(Magic));
if(!File||std::memcmp(Magic,"VFMSH001",8)!=0)
{Error="mesh scene magic is invalid";return false;}
std::uint32_t Version=0,ExpectedTiles=0;
if(!Read(File,Version)||!Read(File,Scene.VoxelSizeMicrons)||!Read(File,ExpectedTiles)
||Version!=1||Scene.VoxelSizeMicrons==0||ExpectedTiles>1000000)
{Error="mesh scene header is malformed";return false;}
for(double& Value:Scene.CameraMeters)if(!Read(File,Value))
{Error="mesh scene camera is truncated";return false;}
if(!Read(File,Scene.YawDegrees)||!Read(File,Scene.PitchDegrees))
{Error="mesh scene camera orientation is truncated";return false;}
Scene.TileCount=ExpectedTiles;
std::uint64_t TotalVertices=0,TotalIndices=0;
for(std::uint32_t Tile=0;Tile<ExpectedTiles;++Tile)
{
std::int32_t Key[4]{};
std::uint32_t SurfaceVertices=0,SkirtCount=0,VertexCount=0,IndexCount=0,MaskCount=0;
for(auto& Value:Key)if(!Read(File,Value)){Error="tile key is truncated";return false;}
if(!Read(File,SurfaceVertices)||!Read(File,SkirtCount)||!Read(File,VertexCount)
||!Read(File,IndexCount)||!Read(File,MaskCount)
||VertexCount>10000000||IndexCount>60000000||MaskCount>20000000
||IndexCount%3!=0||(MaskCount!=0&&MaskCount!=IndexCount/3))
{Error="tile mesh header is malformed";return false;}
if(TotalVertices+VertexCount>std::numeric_limits<std::uint32_t>::max()
||TotalIndices+IndexCount>std::numeric_limits<std::size_t>::max())
{Error="mesh scene exceeds supported index space";return false;}
const std::size_t Base=Scene.Vertices.size();
Scene.Vertices.resize(Base+VertexCount);
Scene.Indices.reserve(Scene.Indices.size()+IndexCount);
for(std::size_t I=Base;I<Base+VertexCount;++I)
for(float& Value:Scene.Vertices[I].Position)if(!Read(File,Value))
{Error="tile positions are truncated";return false;}
for(std::size_t I=Base;I<Base+VertexCount;++I)
{
for(float& Value:Scene.Vertices[I].Normal)if(!Read(File,Value))
{Error="tile normals are truncated";return false;}
Scene.Vertices[I].Height=Scene.Vertices[I].Position[2];
}
const std::size_t IndexOffset=Scene.Indices.size();
Scene.Indices.resize(IndexOffset+IndexCount);
if(IndexCount)File.read(reinterpret_cast<char*>(Scene.Indices.data()+IndexOffset),
static_cast<std::streamsize>(sizeof(std::uint32_t)*IndexCount));
std::vector<std::uint8_t> Masks(MaskCount);
if(MaskCount)File.read(reinterpret_cast<char*>(Masks.data()),MaskCount);
if(!File){Error="tile index or skirt data is truncated";return false;}
for(std::size_t Index=IndexOffset;Index<Scene.Indices.size();++Index)
{
if(Scene.Indices[Index]>=VertexCount)
{Error="tile index points outside its vertex array";return false;}
Scene.Indices[Index]+=static_cast<std::uint32_t>(Base);
}
Scene.Triangles+=IndexCount/3;
Scene.SkirtTriangles+=SkirtCount;
TotalVertices+=VertexCount;TotalIndices+=IndexCount;
}
if(File.peek()!=std::char_traits<char>::eof())
{Error="mesh scene has trailing bytes";return false;}
if(Scene.Indices.empty()){Error="mesh scene contains no triangles";return false;}
return true;
}
EGLDisplay MakeDisplay()
{
using TGetPlatformDisplay=EGLDisplay(*)(EGLenum,void*,const EGLint*);
auto GetPlatformDisplay=reinterpret_cast<TGetPlatformDisplay>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
#ifdef EGL_PLATFORM_SURFACELESS_MESA
if(GetPlatformDisplay)
{
const EGLDisplay Display=GetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY,nullptr);
if(Display!=EGL_NO_DISPLAY)return Display;
}
#endif
return eglGetDisplay(EGL_DEFAULT_DISPLAY);
}
GLuint CompileShader(GLenum Type,const char* Source)
{
const GLuint Shader=glCreateShader(Type);
glShaderSource(Shader,1,&Source,nullptr);
glCompileShader(Shader);
GLint Success=GL_FALSE;
glGetShaderiv(Shader,GL_COMPILE_STATUS,&Success);
if(Success==GL_FALSE)
{
GLint Length=0;glGetShaderiv(Shader,GL_INFO_LOG_LENGTH,&Length);
std::string Log(static_cast<std::size_t>(std::max(Length,1)),0);
glGetShaderInfoLog(Shader,Length,nullptr,Log.data());
std::fprintf(stderr,"[OffscreenGL] shader compile failed: %s\n",Log.c_str());
glDeleteShader(Shader);return 0;
}
return Shader;
}
GLuint MakeProgram()
{
static constexpr char VertexSource[]=R"GLSL(#version 300 es
precision highp float;
layout(location=0) in vec3 inCameraPosition;
layout(location=1) in vec3 inNormal;
layout(location=2) in float inHeight;
out vec3 vNormal;
out float vHeight;
uniform float uTanHalfHorizontal;
uniform float uTanHalfVertical;
uniform float uNear;
uniform float uFar;
void main()
{
float z=max(inCameraPosition.z,0.0001);
float clipZ=((uFar+uNear)/(uFar-uNear))*z-(2.0*uFar*uNear)/(uFar-uNear);
gl_Position=vec4(inCameraPosition.x/uTanHalfHorizontal,
inCameraPosition.y/uTanHalfVertical,clipZ,z);
vNormal=normalize(inNormal);
vHeight=inHeight;
})GLSL";
static constexpr char FragmentSource[]=R"GLSL(#version 300 es
precision highp float;
in vec3 vNormal;
in float vHeight;
out vec4 outColor;
void main()
{
vec3 n=normalize(vNormal);
vec3 normalColor=abs(n)*0.78+vec3(0.12);
float band=mod(floor((vHeight+10000.0)/80.0),2.0);
vec3 bandColor=mix(vec3(0.24,0.31,0.37),vec3(0.39,0.42,0.35),band);
outColor=vec4(mix(bandColor,normalColor,0.68),1.0);
})GLSL";
const GLuint VS=CompileShader(GL_VERTEX_SHADER,VertexSource);
const GLuint FS=CompileShader(GL_FRAGMENT_SHADER,FragmentSource);
if(!VS||!FS)return 0;
const GLuint Program=glCreateProgram();
glAttachShader(Program,VS);glAttachShader(Program,FS);glLinkProgram(Program);
glDeleteShader(VS);glDeleteShader(FS);
GLint Success=GL_FALSE;glGetProgramiv(Program,GL_LINK_STATUS,&Success);
if(Success==GL_FALSE)
{
GLint Length=0;glGetProgramiv(Program,GL_INFO_LOG_LENGTH,&Length);
std::string Log(static_cast<std::size_t>(std::max(Length,1)),0);
glGetProgramInfoLog(Program,Length,nullptr,Log.data());
std::fprintf(stderr,"[OffscreenGL] program link failed: %s\n",Log.c_str());
glDeleteProgram(Program);return 0;
}
return Program;
}
bool WritePng(const std::filesystem::path& Path,int Width,int Height,
const std::vector<std::uint8_t>& Pixels)
{
std::error_code Error;
if(!Path.parent_path().empty())std::filesystem::create_directories(Path.parent_path(),Error);
if(Error)return false;
FILE* File=std::fopen(Path.c_str(),"wb");
if(!File)return false;
png_structp Png=png_create_write_struct(PNG_LIBPNG_VER_STRING,nullptr,nullptr,nullptr);
png_infop Info=Png?png_create_info_struct(Png):nullptr;
if(!Png||!Info){if(Png)png_destroy_write_struct(&Png,nullptr);std::fclose(File);return false;}
if(setjmp(png_jmpbuf(Png)))
{png_destroy_write_struct(&Png,&Info);std::fclose(File);return false;}
png_init_io(Png,File);
png_set_IHDR(Png,Info,static_cast<png_uint_32>(Width),static_cast<png_uint_32>(Height),
8,PNG_COLOR_TYPE_RGBA,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
png_write_info(Png,Info);
const std::size_t Stride=static_cast<std::size_t>(Width)*4;
for(int Y=Height-1;Y>=0;--Y)
png_write_row(Png,const_cast<png_bytep>(Pixels.data()+static_cast<std::size_t>(Y)*Stride));
png_write_end(Png,nullptr);
png_destroy_write_struct(&Png,&Info);
const bool Good=std::fclose(File)==0;
return Good;
}
bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height,
double HorizontalFovDegrees)
{
EGLDisplay Display=MakeDisplay();
EGLint Major=0,Minor=0;
if(Display==EGL_NO_DISPLAY||eglInitialize(Display,&Major,&Minor)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] eglInitialize failed (0x%x)\n",eglGetError());return false;}
if(eglBindAPI(EGL_OPENGL_ES_API)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] eglBindAPI failed (0x%x)\n",eglGetError());eglTerminate(Display);return false;}
const EGLint ConfigAttributes[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT,EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,
EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_DEPTH_SIZE,24,EGL_NONE};
EGLConfig Config{};EGLint ConfigCount=0;
if(eglChooseConfig(Display,ConfigAttributes,&Config,1,&ConfigCount)!=EGL_TRUE||ConfigCount==0)
{std::fprintf(stderr,"[OffscreenEGL] no GLES3 pbuffer config (0x%x)\n",eglGetError());eglTerminate(Display);return false;}
const EGLint SurfaceAttributes[]={EGL_WIDTH,Width,EGL_HEIGHT,Height,EGL_NONE};
EGLSurface Surface=eglCreatePbufferSurface(Display,Config,SurfaceAttributes);
const EGLint ContextAttributes[]={EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE};
EGLContext Context=eglCreateContext(Display,Config,EGL_NO_CONTEXT,ContextAttributes);
if(Surface==EGL_NO_SURFACE||Context==EGL_NO_CONTEXT
||eglMakeCurrent(Display,Surface,Surface,Context)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] context/surface creation failed (0x%x)\n",eglGetError());
if(Context!=EGL_NO_CONTEXT)eglDestroyContext(Display,Context);
if(Surface!=EGL_NO_SURFACE)eglDestroySurface(Display,Surface);
eglTerminate(Display);return false;}
const GLubyte* Renderer=glGetString(GL_RENDERER);
const GLubyte* GlVersion=glGetString(GL_VERSION);
std::printf("[OffscreenEGL] renderer=%s version=%s pbuffer=%dx%d\n",
Renderer?reinterpret_cast<const char*>(Renderer):"unknown",
GlVersion?reinterpret_cast<const char*>(GlVersion):"unknown",Width,Height);
GLuint ColorTexture=0,DepthBuffer=0,Framebuffer=0;
glGenTextures(1,&ColorTexture);glBindTexture(GL_TEXTURE_2D,ColorTexture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,Width,Height,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glGenRenderbuffers(1,&DepthBuffer);glBindRenderbuffer(GL_RENDERBUFFER,DepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT24,Width,Height);
glGenFramebuffers(1,&Framebuffer);glBindFramebuffer(GL_FRAMEBUFFER,Framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,ColorTexture,0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,DepthBuffer);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
{std::fprintf(stderr,"[OffscreenGL] framebuffer is incomplete\n");return false;}
constexpr double Pi=3.14159265358979323846;
const double Yaw=Scene.YawDegrees*Pi/180.0,Pitch=Scene.PitchDegrees*Pi/180.0;
const std::array<double,3> Forward={std::sin(Yaw)*std::cos(Pitch),
std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)};
const std::array<double,3> Right={std::cos(Yaw),-std::sin(Yaw),0.0};
const std::array<double,3> Up={Right[1]*Forward[2]-Right[2]*Forward[1],
Right[2]*Forward[0]-Right[0]*Forward[2],Right[0]*Forward[1]-Right[1]*Forward[0]};
const double TanHalfHorizontal=std::tan(HorizontalFovDegrees*Pi/360.0);
const double Aspect=static_cast<double>(Width)/Height;
const double TanHalfVertical=TanHalfHorizontal/Aspect;
const double Near=0.1,Far=20000.0;
for(FVertex& Vertex:Scene.Vertices)
{
const double DX=Vertex.Position[0]-Scene.CameraMeters[0];
const double DY=Vertex.Position[1]-Scene.CameraMeters[1];
const double DZ=Vertex.Position[2]-Scene.CameraMeters[2];
const float CameraX=static_cast<float>(DX*Right[0]+DY*Right[1]+DZ*Right[2]);
const float CameraY=static_cast<float>(DX*Up[0]+DY*Up[1]+DZ*Up[2]);
const float CameraZ=static_cast<float>(DX*Forward[0]+DY*Forward[1]+DZ*Forward[2]);
Vertex.Position[0]=CameraX;Vertex.Position[1]=CameraY;Vertex.Position[2]=CameraZ;
}
const GLuint Program=MakeProgram();
if(!Program)return false;
GLuint Vao=0,Vbo=0,Ebo=0;
glGenVertexArrays(1,&Vao);glBindVertexArray(Vao);
glGenBuffers(1,&Vbo);glBindBuffer(GL_ARRAY_BUFFER,Vbo);
glBufferData(GL_ARRAY_BUFFER,static_cast<GLsizeiptr>(Scene.Vertices.size()*sizeof(FVertex)),
Scene.Vertices.data(),GL_STATIC_DRAW);
glGenBuffers(1,&Ebo);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,Ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,static_cast<GLsizeiptr>(Scene.Indices.size()*sizeof(std::uint32_t)),
Scene.Indices.data(),GL_STATIC_DRAW);
glEnableVertexAttribArray(0);glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Position)));
glEnableVertexAttribArray(1);glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Normal)));
glEnableVertexAttribArray(2);glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Height)));
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);glClearColor(0.035f,0.055f,0.075f,1.0f);
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(Program);
glUniform1f(glGetUniformLocation(Program,"uTanHalfHorizontal"),static_cast<float>(TanHalfHorizontal));
glUniform1f(glGetUniformLocation(Program,"uTanHalfVertical"),static_cast<float>(TanHalfVertical));
glUniform1f(glGetUniformLocation(Program,"uNear"),static_cast<float>(Near));
glUniform1f(glGetUniformLocation(Program,"uFar"),static_cast<float>(Far));
glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr);
glFinish();
std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4);
glReadPixels(0,0,Width,Height,GL_RGBA,GL_UNSIGNED_BYTE,Pixels.data());
const GLenum GlError=glGetError();
const bool PngGood=GlError==GL_NO_ERROR&&WritePng(Output,Width,Height,Pixels);
if(!PngGood)std::fprintf(stderr,"[OffscreenGL] render or PNG write failed (GL=0x%x)\n",GlError);
else
{
std::error_code SizeError;
const auto Bytes=std::filesystem::file_size(Output,SizeError);
std::printf("[OffscreenCapture] triangles=%llu tiles=%u skirts=%llu png_bytes=%llu status=PASS path=%s\n",
static_cast<unsigned long long>(Scene.Triangles),Scene.TileCount,
static_cast<unsigned long long>(Scene.SkirtTriangles),
static_cast<unsigned long long>(SizeError?0:Bytes),Output.string().c_str());
}
glDeleteProgram(Program);glDeleteBuffers(1,&Ebo);glDeleteBuffers(1,&Vbo);
glDeleteVertexArrays(1,&Vao);glDeleteFramebuffers(1,&Framebuffer);
glDeleteRenderbuffers(1,&DepthBuffer);glDeleteTextures(1,&ColorTexture);
eglMakeCurrent(Display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);
eglDestroyContext(Display,Context);eglDestroySurface(Display,Surface);eglTerminate(Display);
return PngGood;
}
}
int main(int argc,char** argv)
{
if(argc<3)
{
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
return 2;
}
int Width=2560,Height=1440;double Fov=90.0;
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
bool HasCamera=false;std::array<double,3> Camera{};
for(int I=3;I<argc;++I)
{
const std::string_view Arg(argv[I]);
if(Arg.starts_with("--size="))
{
const std::string Value(Arg.substr(7));
if(std::sscanf(Value.c_str(),"%dx%d",&Width,&Height)!=2||Width<=0||Height<=0)
{std::fprintf(stderr,"Invalid image size\n");return 2;}
}
else if(Arg.starts_with("--fov-deg="))
{
char* End=nullptr;const std::string Value(Arg.substr(10));Fov=std::strtod(Value.c_str(),&End);
if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0)
{std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;}
}
else if(Arg.starts_with("--camera-m="))
{
const std::string Value(Arg.substr(11));
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&Camera[0],&Camera[1],&Camera[2])!=3
||!std::isfinite(Camera[0])||!std::isfinite(Camera[1])||!std::isfinite(Camera[2]))
{std::fprintf(stderr,"Invalid camera position\n");return 2;}
HasCamera=true;
}
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
{
const bool IsYaw=Arg.starts_with("--yaw-deg=");
const std::size_t Prefix=IsYaw?10:12;
const std::string Value(Arg.substr(Prefix));char* End=nullptr;
const double Parsed=std::strtod(Value.c_str(),&End);
if(End==Value.c_str()||*End!='\0'||!std::isfinite(Parsed))
{std::fprintf(stderr,"Invalid camera angle\n");return 2;}
if(IsYaw){Yaw=Parsed;HasYaw=true;}else{Pitch=Parsed;HasPitch=true;}
}
else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;}
}
FScene Scene;std::string Error;
if(!LoadScene(argv[1],Scene,Error))
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
if(HasCamera)Scene.CameraMeters=Camera;
if(HasYaw)Scene.YawDegrees=Yaw;
if(HasPitch)Scene.PitchDegrees=Pitch;
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n",
1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2],
Scene.YawDegrees,Scene.PitchDegrees,Scene.Vertices.size(),
static_cast<unsigned long long>(Scene.Triangles));
return Render(Scene,argv[2],Width,Height,Fov)?0:3;
}
+721
View File
@@ -0,0 +1,721 @@
#pragma once
#include "StreamingWorld.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <limits>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace StandaloneScreenSpaceGate
{
inline constexpr double OnePixelRadians=1.57079632679489661923/2560.0;
struct FErrorRow
{
int Lod=0;
std::uint64_t Samples=0,Missing=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0;
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
double MaximumSlopeDegrees=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
std::array<double,4> SlopeP99{},SlopeMaximum{};
bool Valid=false;
};
struct FPopRow
{
int CoarseLod=0;
std::uint64_t Samples=0,Cracks=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0;
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
double MaximumSlopeDegrees=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
std::array<double,4> SlopeP99{},SlopeMaximum{};
bool Valid=false;
};
struct FSurfaceRoot
{
double ZMeters=0.0;
double SlopeX=0.0;
double SlopeY=0.0;
};
struct FViewBasis
{
std::array<double,3> Right{};
std::array<double,3> Up{};
};
struct FErrorAccumulator
{
std::vector<double> Pixels,VerticalPixels;
std::array<std::vector<double>,4> BySlope;
double Maximum=-1.0,MaximumVertical=0.0,MaximumNormalDistanceMeters=0.0;
double MaximumX=0.0,MaximumY=0.0,MaximumSlopeDegrees=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
};
inline double Percentile(std::vector<double>& Values,double P);
inline FViewBasis MakeViewBasis(double YawDegrees,double PitchDegrees)
{
constexpr double Pi=3.14159265358979323846;
const double Yaw=YawDegrees*Pi/180.0,Pitch=PitchDegrees*Pi/180.0;
const std::array<double,3> Forward={std::sin(Yaw)*std::cos(Pitch),
std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)};
FViewBasis Basis;
Basis.Right={std::cos(Yaw),-std::sin(Yaw),0.0};
Basis.Up={Basis.Right[1]*Forward[2]-Basis.Right[2]*Forward[1],
Basis.Right[2]*Forward[0]-Basis.Right[0]*Forward[2],
Basis.Right[0]*Forward[1]-Basis.Right[1]*Forward[0]};
return Basis;
}
inline std::size_t SlopeBin(double Degrees)
{
if(Degrees<30.0)return 0;
if(Degrees<45.0)return 1;
if(Degrees<60.0)return 2;
return 3;
}
inline void AddError(FErrorAccumulator& Accumulator,double DeltaZMeters,
const FSurfaceRoot& Root,double PixelBudgetMeters,const FViewBasis& View,
double X,double Y)
{
constexpr double Pi=3.14159265358979323846;
const double GradientSquared=Root.SlopeX*Root.SlopeX+Root.SlopeY*Root.SlopeY;
const double NormalLength=std::sqrt(1.0+GradientSquared);
const double NormalX=-Root.SlopeX/NormalLength;
const double NormalY=-Root.SlopeY/NormalLength;
const double NormalZ=1.0/NormalLength;
const double NormalDistance=std::abs(DeltaZMeters)/NormalLength;
const double ProjectedNormal=std::hypot(
NormalX*View.Right[0]+NormalY*View.Right[1]+NormalZ*View.Right[2],
NormalX*View.Up[0]+NormalY*View.Up[1]+NormalZ*View.Up[2]);
const double ProjectedVertical=std::hypot(View.Right[2],View.Up[2]);
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
const double VerticalPixels=std::abs(DeltaZMeters)*ProjectedVertical/PixelBudgetMeters;
const double SlopeDegrees=std::atan(std::sqrt(GradientSquared))*180.0/Pi;
Accumulator.Pixels.push_back(NormalPixels);
Accumulator.VerticalPixels.push_back(VerticalPixels);
const std::size_t Bin=SlopeBin(SlopeDegrees);
Accumulator.BySlope[Bin].push_back(NormalPixels);
++Accumulator.SlopeSamples[Bin];
if(NormalPixels>Accumulator.Maximum)
{
Accumulator.Maximum=NormalPixels;
Accumulator.MaximumVertical=VerticalPixels;
Accumulator.MaximumNormalDistanceMeters=NormalDistance;
Accumulator.MaximumX=X;Accumulator.MaximumY=Y;
Accumulator.MaximumSlopeDegrees=SlopeDegrees;
Accumulator.MaximumReferenceZ=Root.ZMeters;
Accumulator.MaximumDisplayedZ=Root.ZMeters+DeltaZMeters;
}
}
inline void AddMissing(FErrorAccumulator& Accumulator)
{
Accumulator.Pixels.push_back(2.0);
Accumulator.VerticalPixels.push_back(2.0);
}
inline void FinalizeError(FErrorRow& Row,FErrorAccumulator& Accumulator)
{
Row.Samples=Accumulator.Pixels.size();
Row.P50=Percentile(Accumulator.Pixels,0.50);
Row.P99=Percentile(Accumulator.Pixels,0.99);
Row.Maximum=Accumulator.Pixels.empty()?0.0
:*std::max_element(Accumulator.Pixels.begin(),Accumulator.Pixels.end());
Row.VerticalP50=Percentile(Accumulator.VerticalPixels,0.50);
Row.VerticalP99=Percentile(Accumulator.VerticalPixels,0.99);
Row.VerticalMaximum=Accumulator.VerticalPixels.empty()?0.0
:*std::max_element(Accumulator.VerticalPixels.begin(),Accumulator.VerticalPixels.end());
Row.MaximumNormalDistanceMeters=Accumulator.MaximumNormalDistanceMeters;
Row.MaximumX=Accumulator.MaximumX;Row.MaximumY=Accumulator.MaximumY;
Row.MaximumReferenceZ=Accumulator.MaximumReferenceZ;
Row.MaximumDisplayedZ=Accumulator.MaximumDisplayedZ;
Row.MaximumSlopeDegrees=Accumulator.MaximumSlopeDegrees;
Row.SlopeSamples=Accumulator.SlopeSamples;
for(std::size_t Bin=0;Bin<Accumulator.BySlope.size();++Bin)
{
Row.SlopeP99[Bin]=Percentile(Accumulator.BySlope[Bin],0.99);
Row.SlopeMaximum[Bin]=Accumulator.BySlope[Bin].empty()?0.0
:*std::max_element(Accumulator.BySlope[Bin].begin(),Accumulator.BySlope[Bin].end());
}
}
struct FHeightGrid
{
int Side=65;
double MinimumX=0.0,MaximumX=0.0,MinimumY=0.0,MaximumY=0.0;
std::vector<std::vector<double>> Hits;
FHeightGrid()=default;
FHeightGrid(int InSide,double MinX,double MaxX,double MinY,double MaxY)
:Side(InSide),MinimumX(MinX),MaximumX(MaxX),MinimumY(MinY),MaximumY(MaxY),
Hits(static_cast<std::size_t>(InSide)*InSide)
{}
double X(int Ix) const
{return MinimumX+(MaximumX-MinimumX)*Ix/(Side-1);}
double Y(int Iy) const
{return MinimumY+(MaximumY-MinimumY)*Iy/(Side-1);}
void Add(int Ix,int Iy,double Z)
{
auto& Values=Hits[static_cast<std::size_t>(Iy)*Side+Ix];
Values.push_back(Z);
}
const std::vector<double>& At(int Ix,int Iy) const
{return Hits[static_cast<std::size_t>(Iy)*Side+Ix];}
};
inline double Percentile(std::vector<double>& Values,double P)
{
if(Values.empty())return 0.0;
std::sort(Values.begin(),Values.end());
const std::size_t Index=std::min(Values.size()-1,
static_cast<std::size_t>(std::ceil(P*Values.size())-1));
return Values[Index];
}
inline double TileAabbDistanceMeters(const FVoxelTileKey& Key,
const FVector& CameraVoxels,double VoxelMeters)
{
const double Extent=static_cast<double>(Key.ExtentVoxels());
const double Min[3]={static_cast<double>(Key.OriginVoxels().X),
static_cast<double>(Key.OriginVoxels().Y),static_cast<double>(Key.OriginVoxels().Z)};
const double Cam[3]={CameraVoxels.X,CameraVoxels.Y,CameraVoxels.Z};
double Squared=0.0;
for(int Axis=0;Axis<3;++Axis)
{
const double Delta=Cam[Axis]<Min[Axis]?Min[Axis]-Cam[Axis]
:Cam[Axis]>Min[Axis]+Extent?Cam[Axis]-(Min[Axis]+Extent):0.0;
Squared+=Delta*Delta;
}
return std::sqrt(Squared)*VoxelMeters;
}
inline bool TileHasNativeSurface(const FVoxelTileKey& Key,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
double VoxelMeters)
{
const double Extent=static_cast<double>(Key.ExtentVoxels());
const double X=(static_cast<double>(Key.OriginVoxels().X)+Extent*0.5)*256.0;
const double Y=(static_cast<double>(Key.OriginVoxels().Y)+Extent*0.5)*256.0;
const double MinZ=static_cast<double>(Key.OriginVoxels().Z);
const double MaxZ=MinZ+Extent;
for(const auto& Plan:Assembly.plan.strates)
{
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(X)),
static_cast<std::int64_t>(std::llround(Y)),0);
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0;
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0;
const double Margin=std::max(2.0,0.25/VoxelMeters);
if(Ground<Cap&&((Ground>=MinZ-Margin&&Ground<=MaxZ+Margin)
||(Cap>=MinZ-Margin&&Cap<=MaxZ+Margin)))return true;
}
return false;
}
inline FHeightGrid Rasterize(std::span<const FStandaloneCpuTile* const> Tiles,
const FVoxelTileKey& Patch,double VoxelMeters,int Side=65)
{
const double Extent=static_cast<double>(Patch.ExtentVoxels())*VoxelMeters;
const double MinX=static_cast<double>(Patch.OriginVoxels().X)*VoxelMeters;
const double MinY=static_cast<double>(Patch.OriginVoxels().Y)*VoxelMeters;
FHeightGrid Grid(Side,MinX,MinX+Extent,MinY,MinY+Extent);
for(const FStandaloneCpuTile* Tile:Tiles)
{
if(!Tile||Tile->Indices.size()<3)continue;
const std::size_t TriangleCount=Tile->Indices.size()/3;
for(std::size_t Triangle=0;Triangle<TriangleCount;++Triangle)
{
if(Triangle<Tile->TriangleSkirtFaceMasks.size()
&&Tile->TriangleSkirtFaceMasks[Triangle]!=0)continue;
const std::uint32_t I0=Tile->Indices[Triangle*3];
const std::uint32_t I1=Tile->Indices[Triangle*3+1];
const std::uint32_t I2=Tile->Indices[Triangle*3+2];
if(I0>=Tile->Vertices.size()||I1>=Tile->Vertices.size()||I2>=Tile->Vertices.size())continue;
const auto& A=Tile->Vertices[I0].Position;
const auto& B=Tile->Vertices[I1].Position;
const auto& C=Tile->Vertices[I2].Position;
const double Denominator=(B[1]-C[1])*(A[0]-C[0])+(C[0]-B[0])*(A[1]-C[1]);
if(std::abs(Denominator)<1e-10)continue;
const double MinTriX=std::min({A[0],B[0],C[0]}),MaxTriX=std::max({A[0],B[0],C[0]});
const double MinTriY=std::min({A[1],B[1],C[1]}),MaxTriY=std::max({A[1],B[1],C[1]});
const double StepX=Extent/(Side-1),StepY=Extent/(Side-1);
const int Ix0=std::clamp(static_cast<int>(std::ceil((MinTriX-MinX)/StepX)),0,Side-1);
const int Ix1=std::clamp(static_cast<int>(std::floor((MaxTriX-MinX)/StepX)),0,Side-1);
const int Iy0=std::clamp(static_cast<int>(std::ceil((MinTriY-MinY)/StepY)),0,Side-1);
const int Iy1=std::clamp(static_cast<int>(std::floor((MaxTriY-MinY)/StepY)),0,Side-1);
for(int Iy=Iy0;Iy<=Iy1;++Iy)
for(int Ix=Ix0;Ix<=Ix1;++Ix)
{
const double X=Grid.X(Ix),Y=Grid.Y(Iy);
const double W0=((B[1]-C[1])*(X-C[0])+(C[0]-B[0])*(Y-C[1]))/Denominator;
const double W1=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/Denominator;
const double W2=1.0-W0-W1;
if(W0<-1e-6||W1<-1e-6||W2<-1e-6)continue;
Grid.Add(Ix,Iy,W0*A[2]+W1*B[2]+W2*C[2]);
}
}
}
for(auto& Values:Grid.Hits)
{
std::sort(Values.begin(),Values.end());
Values.erase(std::unique(Values.begin(),Values.end(),[](double A,double B)
{return std::abs(A-B)<0.02;}),Values.end());
}
return Grid;
}
inline std::vector<FSurfaceRoot> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
const FVoxelTileKey& Patch,double VoxelMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
{
std::vector<FSurfaceRoot> Roots;
const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0;
const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0;
const auto X=static_cast<std::int64_t>(std::llround(XSubvoxels));
const auto Y=static_cast<std::int64_t>(std::llround(YSubvoxels));
const double MinZ=static_cast<double>(Patch.OriginVoxels().Z);
const double MaxZ=MinZ+Patch.ExtentVoxels();
constexpr std::int64_t NormalProbeSubvoxels=256;
for(const auto& Plan:Assembly.plan.strates)
{
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
const auto Column=Field.point(X,Y,0);
if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue;
const auto XMinus=Field.point(X-NormalProbeSubvoxels,Y,0);
const auto XPlus=Field.point(X+NormalProbeSubvoxels,Y,0);
const auto YMinus=Field.point(X,Y-NormalProbeSubvoxels,0);
const auto YPlus=Field.point(X,Y+NormalProbeSubvoxels,0);
const auto Slope=[&](std::int64_t Minus,std::int64_t Plus)
{return static_cast<double>(Plus-Minus)/(2.0*NormalProbeSubvoxels);};
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0*VoxelMeters;
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0*VoxelMeters;
const double PlanMinZ=static_cast<double>(Plan.bottomChunkZ)*32.0*VoxelMeters;
const double PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters;
if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back({Ground,Slope(XMinus.groundHeightSubvoxels,
XPlus.groundHeightSubvoxels),Slope(YMinus.groundHeightSubvoxels,
YPlus.groundHeightSubvoxels)});
if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back({Cap,Slope(XMinus.capHeightSubvoxels,
XPlus.capHeightSubvoxels),Slope(YMinus.capHeightSubvoxels,
YPlus.capHeightSubvoxels)});
}
std::sort(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,const FSurfaceRoot& B)
{return A.ZMeters<B.ZMeters;});
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,
const FSurfaceRoot& B){return std::abs(A.ZMeters-B.ZMeters)<0.01;}),Roots.end());
return Roots;
}
inline bool MatchNearest(const std::vector<double>& Hits,const std::vector<bool>& Used,
double Root,std::size_t& OutIndex,double& OutError)
{
double Best=std::numeric_limits<double>::infinity();std::size_t BestIndex=Hits.size();
for(std::size_t I=0;I<Hits.size();++I)
if(I>=Used.size()||!Used[I])
{
const double Error=std::abs(Hits[I]-Root);
if(Error<Best){Best=Error;BestIndex=I;}
}
if(BestIndex==Hits.size())return false;
OutIndex=BestIndex;OutError=Best;return true;
}
inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch,
double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
{
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
FErrorAccumulator Accumulator;
for(int Iy=0;Iy<MeshGrid.Side;++Iy)
for(int Ix=0;Ix<MeshGrid.Side;++Ix)
{
const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue;
std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false);
for(const FSurfaceRoot& Root:Roots)
{
std::size_t HitIndex=0;double ErrorMeters=0.0;
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root.ZMeters,HitIndex,ErrorMeters))
{
++Row.Missing;
AddMissing(Accumulator);
continue;
}
Used[HitIndex]=true;
AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters,
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
++Row.Samples;
}
// Heightfield2D declares ground and cap roots only. Other mesh intersections may
// be valid tunnel geometry; the exact-mesh comparison checks extra pieces.
}
FinalizeError(Row,Accumulator);
return Row;
}
inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid,
const FHeightGrid& ReferenceGrid,const FVoxelTileKey& Patch,double VoxelMeters,
double PixelBudgetMeters,const VoxelForgeCore::WorldAssemblyManifest& Assembly,
const FViewBasis& View)
{
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
FErrorAccumulator Accumulator;
for(int Iy=0;Iy<ReferenceGrid.Side;++Iy)
for(int Ix=0;Ix<ReferenceGrid.Side;++Ix)
{
const auto& Reference=ReferenceGrid.At(Ix,Iy);
const auto& Mesh=MeshGrid.At(Ix,Iy);
if(Reference.empty()&&Mesh.empty())continue;
const auto Roots=ExactHeightRoots(ReferenceGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
{
const double ReferenceZ=Reference[ReferenceIndex];
UsedReference[ReferenceIndex]=true;
std::size_t MeshIndex=0;double MeshError=0.0;
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
{
++Row.Missing;AddMissing(Accumulator);continue;
}
UsedMesh[MeshIndex]=true;
FSurfaceRoot Root{ReferenceZ,0.0,0.0};
if(!Roots.empty())
{
const auto BestRoot=std::min_element(Roots.begin(),Roots.end(),
[ReferenceZ](const FSurfaceRoot& A,const FSurfaceRoot& B)
{return std::abs(A.ZMeters-ReferenceZ)<std::abs(B.ZMeters-ReferenceZ);});
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
Root=*BestRoot;
}
AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex],
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
}
for(std::size_t I=0;I<Mesh.size();++I)
if(!UsedMesh[I]){++Row.Floating;AddMissing(Accumulator);}
}
FinalizeError(Row,Accumulator);
return Row;
}
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
{
FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true;
FErrorAccumulator Accumulator;
for(int Iy=0;Iy<Coarse.Side;++Iy)
for(int Ix=0;Ix<Coarse.Side;++Ix)
{
const auto& CoarseHits=Coarse.At(Ix,Iy);
const auto& FineHits=Fine.At(Ix,Iy);
const auto Roots=ExactHeightRoots(Coarse,Ix,Iy,Patch,VoxelMeters,Assembly);
if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue;
std::vector<bool> CoarseUsed(CoarseHits.size(),false);
std::vector<bool> FineUsed(FineHits.size(),false);
for(const FSurfaceRoot& Root:Roots)
{
std::size_t CoarseIndex=0,FineIndex=0;
double CoarseError=0.0,FineError=0.0;
if(!MatchNearest(CoarseHits,CoarseUsed,Root.ZMeters,CoarseIndex,CoarseError)
||!MatchNearest(FineHits,FineUsed,Root.ZMeters,FineIndex,FineError))
{
++Row.Cracks;AddMissing(Accumulator);continue;
}
CoarseUsed[CoarseIndex]=true;
FineUsed[FineIndex]=true;
AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex],
Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
++Row.Samples;
}
}
FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
FinalizeError(Stats,Accumulator);
Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
Row.VerticalP50=Stats.VerticalP50;Row.VerticalP99=Stats.VerticalP99;
Row.VerticalMaximum=Stats.VerticalMaximum;
Row.MaximumNormalDistanceMeters=Stats.MaximumNormalDistanceMeters;
Row.MaximumX=Stats.MaximumX;Row.MaximumY=Stats.MaximumY;
Row.MaximumReferenceZ=Stats.MaximumReferenceZ;
Row.MaximumDisplayedZ=Stats.MaximumDisplayedZ;
Row.MaximumSlopeDegrees=Stats.MaximumSlopeDegrees;
Row.SlopeSamples=Stats.SlopeSamples;Row.SlopeP99=Stats.SlopeP99;
Row.SlopeMaximum=Stats.SlopeMaximum;
return Row;
}
inline std::array<double,9> ClosestDisplayedDistancesMeters(
const FStandaloneOwnerWorld& World,const FVector& CameraVoxels,
std::uint32_t VoxelSizeMicrons)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
std::array<double,9> Minimums{};
Minimums.fill(std::numeric_limits<double>::infinity());
for(int Lod=0;Lod<=8;++Lod)
{
const int PhaseCount=1<<Lod;
for(int Phase=0;Phase<PhaseCount;++Phase)
{
FVector Probe=CameraVoxels;
Probe.X=static_cast<float>(Phase*32.0+32.0-1.0/256.0);
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,Probe);
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
if(Tile.Level!=Lod)continue;
Minimums[Lod]=std::min(Minimums[Lod],
TileAabbDistanceMeters(Tile,Probe,VoxelMeters));
}
}
}
for(double& Distance:Minimums)
if(!std::isfinite(Distance))Distance=0.0;
return Minimums;
}
inline std::array<double,9> ClipmapClosestDisplayedDistancesMeters(
const FStandaloneOwnerWorld& World,std::uint32_t VoxelSizeMicrons)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
std::array<double,9> Distances{};
for(int Lod=1;Lod<=8;++Lod)
Distances[Lod]=static_cast<double>(World.ClipRadius)
*static_cast<double>(CHUNK_SIZE)*(1ull<<(Lod-1))*VoxelMeters;
return Distances;
}
inline void PrintSlopeTail(const char* Kind,int Lod,const std::array<std::uint64_t,4>& Counts,
const std::array<double,4>& P99,const std::array<double,4>& Maximum)
{
constexpr const char* Labels[4]={"lt30","30to45","45to60","ge60"};
for(std::size_t Bin=0;Bin<Counts.size();++Bin)
std::printf("[ScreenSpaceSlopeTail] kind=%s lod=%d slope_deg=%s samples=%llu p99_px=%.4f max_px=%.4f\n",
Kind,Lod,Labels[Bin],static_cast<unsigned long long>(Counts[Bin]),P99[Bin],Maximum[Bin]);
}
inline void PrintKnownFailureControl(double CellMeters,double DisplayDistanceMeters,
const FViewBasis& View)
{
constexpr double Slope=100.0;
const double LateralMeters=CellMeters/200.0;
const double VerticalMeters=Slope*LateralMeters;
const double NormalLength=std::sqrt(1.0+Slope*Slope);
const double NormalDistance=VerticalMeters/NormalLength;
const std::array<double,3> Normal={-Slope/NormalLength,0.0,1.0/NormalLength};
const double ProjectedNormal=std::hypot(
Normal[0]*View.Right[0]+Normal[1]*View.Right[1]+Normal[2]*View.Right[2],
Normal[0]*View.Up[0]+Normal[1]*View.Up[1]+Normal[2]*View.Up[2]);
const double PixelBudgetMeters=DisplayDistanceMeters*OnePixelRadians;
const double VerticalPixels=VerticalMeters/PixelBudgetMeters;
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
const bool Passed=VerticalPixels>4.0&&NormalPixels<1.0;
std::printf("[ScreenSpaceKnownFailureControl] case=steep-plane-lateral-offset slope_deg=%.4f cell_m=%.6f lateral_m=%.6f cell_fraction=%.6f vertical_error_m=%.6f vertical_height_px=%.4f closest_normal_m=%.6f projected_normal_px=%.4f status=%s\n",
std::atan(Slope)*180.0/3.14159265358979323846,CellMeters,LateralMeters,
LateralMeters/CellMeters,VerticalMeters,VerticalPixels,NormalDistance,NormalPixels,
Passed?"EXPECTED_VERTICAL_FALSE_POSITIVE":"CONTROL_FAILED");
}
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
FStandaloneStreamingGenerator& Streamer,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels,
double CameraYawDegrees,double CameraPitchDegrees)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
std::array<double,9> Budgets{};
std::array<FErrorRow,9> ErrorRows{};
std::array<FPopRow,9> PopRows{};
bool GatePass=true;
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=local_surface_normal_projected field_reference=direct-Heightfield2D-roots normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n",
OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
{
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
std::printf("[ScreenSpaceDistance] lod=%d closest_m=%.3f pixel_budget_m=%.6f pixel_budget_cm=%.3f pixel_budget_subvoxels=%lld rule=%s\n",
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
}
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
{
if(!SampleTiles[Lod]||SampleTiles[Lod]->Vertices.empty())
{
std::printf("[ScreenSpaceGateLOD] lod=%d status=NO_SURFACE_TILE\n",Lod);
GatePass=false;continue;
}
const FStandaloneCpuTile& CoarseTile=*SampleTiles[Lod];
const FStandaloneCpuTile* CoarsePtr=&CoarseTile;
const std::span<const FStandaloneCpuTile* const> CoarseSpan(&CoarsePtr,1);
const FHeightGrid CoarseGrid=Rasterize(CoarseSpan,CoarseTile.Key,VoxelMeters);
const FStandaloneCpuTile ExactTile=Streamer.GenerateDiagnostic(
CoarseTile.Key,0,1,true);
const FStandaloneCpuTile* ExactPointer=&ExactTile;
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly,View);
const FErrorRow& Error=ErrorRows[Lod];
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
&&Error.Samples>=16&&Error.Maximum<=1.0;
GatePass=GatePass&&LodPass;
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xy_m=(%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z,
static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum,
Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum,
Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY,
Error.MaximumSlopeDegrees,
static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating),
LodPass?"PASS":"REJECT");
PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum);
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly,View);
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
GatePass=GatePass&&FieldPass;
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99,
FieldError.VerticalMaximum,FieldError.MaximumNormalDistanceMeters,
FieldError.MaximumX,FieldError.MaximumY,FieldError.MaximumDisplayedZ,
FieldError.MaximumSlopeDegrees,
static_cast<unsigned long long>(FieldError.Missing),
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99,
FieldError.SlopeMaximum);
bool FaultControlsPass=true;
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
ExactGrid.MinimumY,ExactGrid.MaximumY);
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&MissingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
Lod,static_cast<unsigned long long>(MissingError.Missing),
MissingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
FStandaloneCpuTile FloatingTile=CoarseTile;
const std::uint32_t FloatingVertexBase=static_cast<std::uint32_t>(FloatingTile.Vertices.size());
for(FStandaloneCpuVertex Vertex:CoarseTile.Vertices)
{
Vertex.Position[2]+=2.0f;
FloatingTile.Vertices.push_back(Vertex);
}
for(const std::uint32_t Index:CoarseTile.Indices)
FloatingTile.Indices.push_back(FloatingVertexBase+Index);
FloatingTile.TriangleSkirtFaceMasks.insert(FloatingTile.TriangleSkirtFaceMasks.end(),
CoarseTile.TriangleSkirtFaceMasks.begin(),CoarseTile.TriangleSkirtFaceMasks.end());
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&FloatingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
Lod,static_cast<unsigned long long>(FloatingError.Floating),FloatingError.Maximum,
FloatingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
std::vector<FStandaloneCpuTile> FineTiles;
FineTiles.reserve(8);
for(int Dz=0;Dz<=1;++Dz)
for(int Dy=0;Dy<=1;++Dy)
for(int Dx=0;Dx<=1;++Dx)
{
const FVoxelTileKey FineKey(FIntVector(2*CoarseTile.Key.Coord.X+Dx,
2*CoarseTile.Key.Coord.Y+Dy,2*CoarseTile.Key.Coord.Z+Dz),Lod-1);
FStandaloneCpuTile FineTile=Streamer.GenerateDiagnostic(FineKey);
if(!FineTile.Vertices.empty())FineTiles.push_back(std::move(FineTile));
}
std::vector<const FStandaloneCpuTile*> FinePointers;
FinePointers.reserve(FineTiles.size());
for(const auto& Tile:FineTiles)FinePointers.push_back(&Tile);
const auto FineSpan=std::span<const FStandaloneCpuTile* const>(
FinePointers.data(),FinePointers.size());
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly,View);
const FPopRow& Pop=PopRows[Lod];
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
GatePass=GatePass&&PopPass;
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f cracks=%llu floating=%llu status=%s\n",
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum,
Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY,
Pop.MaximumDisplayedZ,
Pop.MaximumSlopeDegrees,static_cast<unsigned long long>(Pop.Cracks),
static_cast<unsigned long long>(Pop.Floating),
PopPass?"PASS":"REJECT");
PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum);
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&CrackRejected;
GatePass=GatePass&&FaultControlsPass;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-fine-ring-surface cracks=%llu max_px=%.4f status=%s\n",
Lod,static_cast<unsigned long long>(MissingFineError.Cracks),
MissingFineError.Maximum,CrackRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
bool NegativeControlsPass=true;
const auto CheckNegative=[&](const char* Name,std::uint32_t Drop,std::uint32_t Stride)
{
FStandaloneCpuTile Bad=Streamer.GenerateDiagnostic(
CoarseTile.Key,Drop,Stride,true);
const FStandaloneCpuTile* BadPointer=&Bad;
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool Rejected=BadError.Missing>0||BadError.Floating>0
||BadError.Samples<16||BadError.Maximum>1.0;
NegativeControlsPass=NegativeControlsPass&&Rejected;
std::printf("[ScreenSpaceNegative] lod=%d case=%s p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
Lod,Name,BadError.P50,BadError.P99,BadError.Maximum,
static_cast<unsigned long long>(BadError.Missing),
static_cast<unsigned long long>(BadError.Floating),
Rejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
};
CheckNegative("s13-global-octave-drop",1,1);
CheckNegative("stride-2-column-interpolation",0,2);
GatePass=GatePass&&NegativeControlsPass;
}
std::printf("[ScreenSpaceGateResult] status=%s\n",GatePass?"PASS":"REJECT");
return GatePass;
}
}
+20 -3
View File
@@ -37,6 +37,7 @@ namespace
bool ProfileColumnOverlap=false;
bool ForceGenerate=false;
bool MeshCoverage=false;
bool ScreenSpaceGate=false;
bool HasSeedOverride=false;
int SeedOverride=0;
bool HasRenderDistanceOverride=false;
@@ -48,6 +49,9 @@ namespace
bool HasCamera=false;
bool AutoSurfaceCamera=false;
FVector CameraMeters=FVector::ZeroVector;
float CameraYawDegrees=0.0f;
float CameraPitchDegrees=0.0f;
std::filesystem::path MeshExportPath;
};
bool ParseInteger(std::string_view text,int& output)
@@ -61,13 +65,15 @@ namespace
return true;
}
bool ParseCamera(std::string_view text,FVector& meters)
bool ParseCamera(std::string_view text,FVector& meters,float& yawDegrees,float& pitchDegrees)
{
const std::string values(text);
float x=0.0f,y=0.0f,z=0.0f,yaw=0.0f,pitch=0.0f;
if(std::sscanf(values.c_str(),"%f,%f,%f,%f,%f",&x,&y,&z,&yaw,&pitch)!=5)
return false;
meters={x,y,z};
yawDegrees=yaw;
pitchDegrees=pitch;
return true;
}
@@ -85,10 +91,12 @@ namespace
std::printf("Usage: %s [owner_world.json] --world-assembler-prototype --stream-benchmark --stream-pipeline [options]\n"
" --workers=N --clip-radius=N --max-clip-level=N --max-level=N\n"
" --camera=X,Y,Z,YAW,PITCH (position in metres) --stream-hash\n"
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands\n"
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
" --screen-space-gate (measure display error and historical negative controls)\n"
" --render-distance-chunks=N (0 disables the extra far ring)\n"
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
@@ -103,6 +111,7 @@ namespace
else if(arg=="--profile-column-overlap")options.ProfileColumnOverlap=true;
else if(arg=="--force-generate")options.ForceGenerate=true;
else if(arg=="--mesh-coverage")options.MeshCoverage=true;
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
else if(arg.starts_with("--recipe="))
{
@@ -135,10 +144,17 @@ namespace
}
else if(arg.starts_with("--camera="))
{
if(!ParseCamera(arg.substr(9),options.CameraMeters))
if(!ParseCamera(arg.substr(9),options.CameraMeters,
options.CameraYawDegrees,options.CameraPitchDegrees))
{std::fprintf(stderr,"Invalid --camera value: %.*s\n",static_cast<int>(arg.size()),arg.data());return false;}
options.HasCamera=true;
}
else if(arg.starts_with("--mesh-export="))
{
options.MeshExportPath=std::filesystem::path(arg.substr(14));
if(options.MeshExportPath.empty())
{std::fprintf(stderr,"--mesh-export needs a file path\n");return false;}
}
else if(arg.starts_with("--workers="))
{if(!readInt("--workers=",options.WorkerCount))return false;}
else if(arg.starts_with("--clip-radius="))
@@ -297,5 +313,6 @@ int main(int argc,char** argv)
cameraVoxels,options.WorkerCount,options.MaxLevel,VoxelForgeCore::DetailVariant::coherent,
scheduler,options.StreamHash,false,false,false,false,coldStart,
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
options.MeshCoverage);
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
options.CameraPitchDegrees,options.ScreenSpaceGate);
}
+207 -4
View File
@@ -2,6 +2,7 @@
#include "OwnerWorldLoader.h"
#include "StreamingWorld.h"
#include "LinuxScreenSpaceGate.h"
#include <algorithm>
#include <array>
@@ -9,6 +10,8 @@
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <limits>
#include <thread>
#include <unordered_map>
@@ -17,6 +20,76 @@
namespace StandaloneStreamBenchmark
{
struct FSceneExportWriter
{
std::ofstream File;
bool bGood=true;
std::uint32_t TileCount=0;
template<typename T> void Write(const T& Value)
{
File.write(reinterpret_cast<const char*>(&Value),sizeof(Value));
bGood=bGood&&static_cast<bool>(File);
}
bool Open(const std::filesystem::path& Path,std::uint32_t VoxelSizeMicrons,
const FVector& CameraVoxels,float YawDegrees,float PitchDegrees,
std::uint32_t ExpectedTiles)
{
std::error_code Error;
if(!Path.parent_path().empty())
std::filesystem::create_directories(Path.parent_path(),Error);
if(Error)return false;
File.open(Path,std::ios::binary|std::ios::trunc);
if(!File)return false;
constexpr char Magic[8]={'V','F','M','S','H','0','0','1'};
File.write(Magic,sizeof(Magic));
const std::uint32_t Version=1;
Write(Version);Write(VoxelSizeMicrons);Write(ExpectedTiles);
const double MicronsToMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
const double Camera[3]={CameraVoxels.X*MicronsToMeters,
CameraVoxels.Y*MicronsToMeters,CameraVoxels.Z*MicronsToMeters};
for(const double Value:Camera)Write(Value);
const double Yaw=YawDegrees,Pitch=PitchDegrees;
Write(Yaw);Write(Pitch);
return bGood;
}
void Add(const FStandaloneCpuTile& Tile)
{
const std::int32_t Key[4]={Tile.Key.Coord.X,Tile.Key.Coord.Y,
Tile.Key.Coord.Z,Tile.Key.Level};
for(const auto Value:Key)Write(Value);
const std::uint32_t SurfaceVertexCount=Tile.SurfaceVertexCount;
const std::uint32_t SkirtTriangleCount=Tile.SkirtTriangleCount;
const std::uint32_t VertexCount=static_cast<std::uint32_t>(Tile.Vertices.size());
const std::uint32_t IndexCount=static_cast<std::uint32_t>(Tile.Indices.size());
const std::uint32_t MaskCount=static_cast<std::uint32_t>(Tile.TriangleSkirtFaceMasks.size());
Write(SurfaceVertexCount);Write(SkirtTriangleCount);
Write(VertexCount);Write(IndexCount);Write(MaskCount);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Position)Write(Value);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Normal)Write(Value);
if(IndexCount)
File.write(reinterpret_cast<const char*>(Tile.Indices.data()),
static_cast<std::streamsize>(sizeof(std::uint32_t)*Tile.Indices.size()));
if(MaskCount)
File.write(reinterpret_cast<const char*>(Tile.TriangleSkirtFaceMasks.data()),
static_cast<std::streamsize>(Tile.TriangleSkirtFaceMasks.size()));
bGood=bGood&&static_cast<bool>(File);
++TileCount;
}
bool Close(std::uint32_t ExpectedTiles)
{
File.flush();
const bool bComplete=bGood&&static_cast<bool>(File)&&TileCount==ExpectedTiles;
File.close();
return bComplete;
}
};
inline void HashWord(uint64& Hash,uint64 Word,int32 Bytes=8)
{
for(int32 I=0;I<Bytes;++I)
@@ -62,10 +135,26 @@ namespace StandaloneStreamBenchmark
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap,
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false)
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false,
const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f,
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly();
std::array<double,9> ClosestDisplayDistances{};
std::array<std::int64_t,9> PixelBudgetsSubvoxels{};
if(bScreenSpaceGate)
{
ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons);
const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor(
ClosestDisplayDistances[static_cast<std::size_t>(Lod)]
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
}
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
VoxelForgeCore::World Shape(CoreSnapshot);
@@ -154,6 +243,7 @@ namespace StandaloneStreamBenchmark
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels);
if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true);
const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
@@ -162,6 +252,22 @@ namespace StandaloneStreamBenchmark
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles but queued %d.\n",
Desired.Tiles.size(),Update.DesiredTiles);return 7;
}
FSceneExportWriter SceneExport;
const bool bExportRequested=!MeshExportPath.empty();
bool bSceneExportPassing=true;
if(bExportRequested)
{
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
static_cast<std::uint32_t>(Update.DesiredTiles));
if(!bSceneExportPassing)
{
Streamer.Stop();
std::fprintf(stderr,"[MeshSceneExport] FAIL: could not open %s\n",
MeshExportPath.string().c_str());
return 8;
}
}
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows;
int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0;
@@ -196,6 +302,8 @@ namespace StandaloneStreamBenchmark
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
std::array<double,9> MeshAssemblyMsByLOD{};
std::array<double,9> SkirtMsByLOD{};
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
std::array<std::uint64_t,9> TileCountByLOD{};
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
@@ -214,6 +322,9 @@ namespace StandaloneStreamBenchmark
double NativeMeshingMilliseconds=0.0;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level<World.FullResClipLevels)++FullDetailExpected;
std::array<std::optional<FStandaloneCpuTile>,9> GateSampleTiles{};
std::array<double,9> GateSampleDistances{};
GateSampleDistances.fill(std::numeric_limits<double>::infinity());
Clock::time_point NeighborhoodReady{},FullDetailReady{},WholeViewReady{};
bool bNeighborhoodReported=false,bFullDetailReported=false;
while(Finished<Update.DesiredTiles)
@@ -222,6 +333,7 @@ namespace StandaloneStreamBenchmark
if(Ready.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;}
for(const FStandaloneCpuTile& Tile:Ready)
{
if(bExportRequested)SceneExport.Add(Tile);
++Finished;
if(bReportMeshCoverage)
{
@@ -239,6 +351,23 @@ namespace StandaloneStreamBenchmark
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
&&!Tile.Vertices.empty()
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0))
{
const double Distance=StandaloneScreenSpaceGate::TileAabbDistanceMeters(
Tile.Key,CameraPositionVoxels,
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0);
const bool bCloser=Distance<GateSampleDistances[LOD]-1e-9;
const bool bTied=std::abs(Distance-GateSampleDistances[LOD])<=1e-9;
const bool bKeyLess=!GateSampleTiles[LOD]
||TileKeyLessForHash(Tile.Key,GateSampleTiles[LOD]->Key);
if(bCloser||(bTied&&bKeyLess))
{GateSampleDistances[LOD]=Distance;GateSampleTiles[LOD]=Tile;}
}
}
if(bProfileColumnOverlap&&Tile.Key.Level>=0
&&Tile.Key.Level<static_cast<int32>(ColumnCaptureTilesByLOD.size()))
@@ -321,6 +450,28 @@ namespace StandaloneStreamBenchmark
}
}
Streamer.Stop();
bool bScreenSpaceGatePassing=true;
if(bScreenSpaceGate)
{
if(Assembly)
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
CameraPitchDegrees);
else
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
}
if(bExportRequested)
{
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Update.DesiredTiles));
std::error_code FileSizeError;
const std::uintmax_t ExportBytes=std::filesystem::file_size(MeshExportPath,FileSizeError);
std::printf("[MeshSceneExport] tiles=%u expected=%d bytes=%llu status=%s path=%s\n",
SceneExport.TileCount,Update.DesiredTiles,
static_cast<unsigned long long>(FileSizeError?0:ExportBytes),
bSceneExportPassing?"PASS":"FAIL",MeshExportPath.string().c_str());
bSceneExportPassing=bSceneExportPassing&&!FileSizeError;
}
bool bColumnOverlapCapturePassing=true;
if(bProfileColumnOverlap)
{
@@ -341,10 +492,12 @@ namespace StandaloneStreamBenchmark
}
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
{
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f mesh_assembly_ms=%.3f\n",
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f skirt_ms=%.3f skirt_triangles=%llu mesh_assembly_ms=%.3f\n",
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD],
ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD],
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]);
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
MeshAssemblyMsByLOD[LOD]);
}
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
@@ -393,9 +546,59 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(HashTileRows(MeshRows)),
static_cast<unsigned long long>(HashTileRows(PayloadRows)),
static_cast<unsigned long long>(HashTileRows(TopologyRows)));
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
{
const auto FilterLOD=[LOD](const std::vector<FHashedTile>& Rows)
{
std::vector<FHashedTile> Selected;
for(const FHashedTile& Row:Rows)
if(Row.Key.Level==static_cast<int32>(LOD))Selected.push_back(Row);
return Selected;
};
auto LodDensity=FilterLOD(DensityRows),LodMesh=FilterLOD(MeshRows);
auto LodPayload=FilterLOD(PayloadRows),LodTopology=FilterLOD(TopologyRows);
std::printf("[PipelineLODHash] lod=%zu tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx\n",
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),
static_cast<unsigned long long>(HashTileRows(LodDensity)),
static_cast<unsigned long long>(HashTileRows(LodMesh)),
static_cast<unsigned long long>(HashTileRows(LodPayload)),
static_cast<unsigned long long>(HashTileRows(LodTopology)));
}
const std::array<VoxelForgeCore::Vec3d,9> QueryPoints={{
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X+8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X-8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y+8.0,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y-8.0,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z+8.0},
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z-8.0},
{CameraPositionVoxels.X+64.0,CameraPositionVoxels.Y-32.0,CameraPositionVoxels.Z+16.0},
{CameraPositionVoxels.X-128.0,CameraPositionVoxels.Y+96.0,CameraPositionVoxels.Z-64.0}}};
uint64 ExactDensityHash=14695981039346656037ull;
uint64 ExactUnclampedHash=14695981039346656037ull;
uint64 ExactDetailHash=14695981039346656037ull;
uint64 ExactCollisionHash=14695981039346656037ull;
for(const auto& Point:QueryPoints)
{
const float Density=Shape.densityAt(Point);
const float Unclamped=Shape.unclampedDensityAt(Point);
const auto Detail=Shape.detailSampleAt(Point);
HashWord(ExactDensityHash,std::bit_cast<uint32>(Density),4);
HashWord(ExactUnclampedHash,std::bit_cast<uint32>(Unclamped),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.displacement),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.caveInfluence),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.floorInfluence),4);
HashWord(ExactCollisionHash,Shape.isSolidAt(Point)?1u:0u,1);
}
std::printf("[PipelineExactQueryHash] probes=%zu density=0x%016llx unclamped=0x%016llx detail=0x%016llx collision=0x%016llx path=direct-Core-queries\n",
QueryPoints.size(),static_cast<unsigned long long>(ExactDensityHash),
static_cast<unsigned long long>(ExactUnclampedHash),
static_cast<unsigned long long>(ExactDetailHash),
static_cast<unsigned long long>(ExactCollisionHash));
}
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)?0:6;
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)
&&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing?0:6;
}
} // namespace StandaloneStreamBenchmark
+20 -2
View File
@@ -646,6 +646,16 @@ public:
int32 GetWorkerCount()const{return WorkerCount;}
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
void SetMesherStageProfilingEnabled(bool Enabled)
{bProfileMesherStages=Enabled;}
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
bool ForceGenerate=false)
{
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
}
private:
FStandaloneStreamingUpdate UpdateDesiredSet(FStandaloneDesiredTileSet NewDesired,
const std::vector<FVoxelTileKey>& ResidentKeys,uint64 CrossingIndex)
@@ -761,12 +771,14 @@ private:
Wake.notify_all();
return Stats;
}
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
bool ForceGenerate=false)
{
const auto StartTime=Job.StartedAt;
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
if(MeshCache)
if(MeshCache&&!ForceGenerate)
{
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
{
@@ -805,6 +817,11 @@ private:
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
Options.diagnosticColumnInterpolationStride=
std::max<std::uint32_t>(1,DiagnosticCellStride);
std::string Error;
auto Result=CoreWorld->generateTile(
{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error);
@@ -977,6 +994,7 @@ private:
bool bSkipColors=false;
bool bCaptureColumnCoordinates=false;
int32 Lod8HorizonInteriorStepVoxels=0;
bool bProfileMesherStages=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
std::condition_variable Wake;
@@ -1768,6 +1768,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
const uint64 SkirtStageStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
{
const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize;
@@ -2007,6 +2008,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
EmitSkirts(GroundTris,GroundSkirtFaceMasks);
EmitSkirts(CapTris,CapSkirtFaceMasks);
}
if(OutStageTimings)
OutStageTimings->SkirtMilliseconds=static_cast<double>(
FPlatformTime::Cycles64()-SkirtStageStartCycles)
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
// FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change.
@@ -2059,6 +2064,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls);
OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles(
AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls);
OutStageTimings->SkirtMilliseconds=std::max(0.0,
OutStageTimings->SkirtMilliseconds);
const double TotalMilliseconds=static_cast<double>(
FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs;
OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds
@@ -2066,7 +2073,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
-OutStageTimings->CellClassificationMilliseconds
-OutStageTimings->GradientNormalsMilliseconds
-OutStageTimings->EdgeInterpolationMilliseconds
-OutStageTimings->VertexDedupStreamMilliseconds);
-OutStageTimings->VertexDedupStreamMilliseconds
-OutStageTimings->SkirtMilliseconds);
}
return MeshData;
@@ -50,6 +50,7 @@ struct FVoxelMarchingCubesStageTimings
double GradientNormalsMilliseconds = 0.0;
double EdgeInterpolationMilliseconds = 0.0;
double VertexDedupStreamMilliseconds = 0.0;
double SkirtMilliseconds = 0.0;
double OtherMilliseconds = 0.0;
uint64 GradientFallbackDensityCalls = 0;
uint64 EdgeMidpointDensityCalls = 0;