Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,9 @@
[submodule "3rdparty/OffsetAllocator"]
path = 3rdparty/OffsetAllocator
url = https://github.com/sebbbi/OffsetAllocator
[submodule "3rdparty/spz"]
path = 3rdparty/spz
url = https://github.com/nianticlabs/spz
[submodule "3rdparty/zstd"]
path = 3rdparty/zstd
url = https://github.com/facebook/zstd
1 change: 1 addition & 0 deletions 3rdparty/3rdparty.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ include(3rdparty/mimalloc.cmake)
include(3rdparty/sh4lt.cmake)
include(3rdparty/shmdata.cmake)
include(3rdparty/snappy.cmake)
include(3rdparty/zstd.cmake)
endif()
1 change: 1 addition & 0 deletions 3rdparty/spz
Submodule spz added at 7ae162
1 change: 1 addition & 0 deletions 3rdparty/zstd
Submodule zstd added at 885c79
49 changes: 49 additions & 0 deletions 3rdparty/zstd.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
if(SCORE_USE_SYSTEM_LIBRARIES)
find_package(zstd GLOBAL CONFIG)
endif()

if(NOT TARGET zstd::libzstd_static AND NOT TARGET zstd::libzstd_shared AND NOT TARGET zstd)
set(ZSTD_BUILD_PROGRAMS OFF CACHE INTERNAL "" FORCE)
set(ZSTD_BUILD_TESTS OFF CACHE INTERNAL "" FORCE)
set(ZSTD_BUILD_SHARED OFF CACHE INTERNAL "" FORCE)
set(ZSTD_BUILD_STATIC ON CACHE INTERNAL "" FORCE)
set(ZSTD_BUILD_DICTBUILDER OFF CACHE INTERNAL "" FORCE)

set(old_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)

if(NOT MSVC AND NOT CMAKE_CROSSCOMPILING)
if(CMAKE_BUILD_TYPE MATCHES ".*Deb.*")
set(old_CFLAGS "${CMAKE_C_FLAGS}")
set(old_CXXFLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native")
endif()
endif()

add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zstd/build/cmake" EXCLUDE_FROM_ALL)

if(NOT MSVC AND NOT CMAKE_CROSSCOMPILING)
if(CMAKE_BUILD_TYPE MATCHES ".*Deb.*")
set(CMAKE_C_FLAGS "${old_CFLAGS}")
set(CMAKE_CXX_FLAGS "${old_CXXFLAGS}")
endif()
endif()

set(BUILD_SHARED_LIBS ${old_BUILD_SHARED_LIBS})
endif()

# Make later find_package(zstd) calls (e.g. 3rdparty/spz) resolve to the
# targets configured above: some prebuilt SDKs ship zstd configs pointing to
# files that do not exist, and a not-found result would trigger FetchContent
# fallbacks that clash with the vendored targets.
file(WRITE "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/zstd-config.cmake" [=[
if(TARGET libzstd_static AND NOT TARGET zstd::libzstd_static)
add_library(zstd::libzstd_static INTERFACE IMPORTED GLOBAL)
target_link_libraries(zstd::libzstd_static INTERFACE libzstd_static)
endif()
if(TARGET libzstd_shared AND NOT TARGET zstd::libzstd_shared)
add_library(zstd::libzstd_shared INTERFACE IMPORTED GLOBAL)
target_link_libraries(zstd::libzstd_shared INTERFACE libzstd_shared)
endif()
]=])
32 changes: 31 additions & 1 deletion src/plugins/score-plugin-threedim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ if(NOT TARGET fastgltf)
add_subdirectory("${3RDPARTY_FOLDER}/fastgltf" "${CMAKE_CURRENT_BINARY_DIR}/fastgltf" EXCLUDE_FROM_ALL)
endif()

# spz — Niantic / Adobe reference SPZ decoder for compressed 3DGS files.
# v1-3 only (v4/ZSTD stubbed; see 3rdparty/spz/CMakeLists.txt). Pulls
# in zlib via ZLIB::ZLIB.
if(NOT TARGET spz)
set(SPZ_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(SPZ_BUILD_PYTHON_BINDINGS OFF CACHE BOOL "" FORCE)
set(SPZ_BUILD_EXTENSIONS OFF CACHE BOOL "" FORCE)
add_subdirectory("${3RDPARTY_FOLDER}/spz" "${CMAKE_CURRENT_BINARY_DIR}/spz" EXCLUDE_FROM_ALL)

if(MSVC)
# The prebuilt sysroot's zconf.h includes <unistd.h> unconditionally;
# provide a stub so spz's zlib usage compiles with MSVC / clang-cl.
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/spz-msvc-compat/unistd.h"
"#include <io.h>\n#include <process.h>\n")
target_include_directories(spz PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/spz-msvc-compat")
endif()
endif()

# libssynth
add_library(
ssynth STATIC
Expand Down Expand Up @@ -158,6 +176,8 @@ add_library(
Threedim/InjectBuffer.cpp
Threedim/InjectTexture.hpp
Threedim/InjectTexture.cpp
Threedim/TagAs.hpp
Threedim/TagAs.cpp
Threedim/PBRMesh.hpp
Threedim/PBRMesh.cpp
Threedim/MaterialOverride.hpp
Expand Down Expand Up @@ -199,6 +219,16 @@ add_library(
Threedim/PCLToGeometry.cpp
Threedim/Ply.hpp
Threedim/Ply.cpp
Threedim/PrimitiveCloud/PlyParser.hpp
Threedim/PrimitiveCloud/PlyParser.cpp
Threedim/PrimitiveCloud/SplatBinary.hpp
Threedim/PrimitiveCloud/SplatBinary.cpp
Threedim/PrimitiveCloud/SpzCodec.hpp
Threedim/PrimitiveCloud/SpzCodec.cpp
Threedim/PrimitiveCloud/SceneFromCloud.hpp
Threedim/PrimitiveCloud/SceneFromCloud.cpp
Threedim/PrimitiveCloud/FormatOverride.hpp
Threedim/PrimitiveCloud/FormatOverride.cpp
Threedim/Primitive.hpp
Threedim/Primitive.cpp
Threedim/StructureSynth.hpp
Expand Down Expand Up @@ -255,4 +285,4 @@ target_include_directories(
target_link_libraries(
score_plugin_threedim PRIVATE score_plugin_engine score_plugin_avnd
score_plugin_gfx fmt::fmt ssynth Eigen3::Eigen
fastgltf::fastgltf)
fastgltf::fastgltf spz)
95 changes: 80 additions & 15 deletions src/plugins/score-plugin-threedim/Threedim/AssetLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
#include "FbxParser.hpp"
#include "GltfParser.hpp"
#include "Ply.hpp"
#include "PrimitiveCloud/FormatOverride.hpp"
#include "PrimitiveCloud/PlyParser.hpp"
#include "PrimitiveCloud/SceneFromCloud.hpp"
#include "PrimitiveCloud/SplatBinary.hpp"
#include "PrimitiveCloud/SpzCodec.hpp"
#include "SceneFromMeshes.hpp"
#include "VcgImporters.hpp"

Expand Down Expand Up @@ -155,12 +160,7 @@ AssetLoader::ins::asset_t::process(file_type tv)
}
else if(hasSuffixCI(fname, "gltf") || hasSuffixCI(fname, "glb"))
{
auto t0 = std::chrono::steady_clock::now();
loaded = runInnerParser<GltfParser>(tv, &GltfParser::ins::gltf_t::process);
auto t1 = std::chrono::steady_clock::now();
qDebug() << "LOADING TIME"
<< (std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0) / 1e6)
.count();
}
else if(hasSuffixCI(fname, "obj"))
{
Expand All @@ -176,14 +176,32 @@ AssetLoader::ins::asset_t::process(file_type tv)
}
else if(hasSuffixCI(fname, "ply"))
{
Threedim::float_vec buf;
auto meshes = Threedim::PlyFromFile(fname, buf);
if(!meshes.empty())
// Sniff the header first: a PLY whose vertex element carries
// splat-style columns (or no face element) goes through the
// primitive-cloud path; everything else stays on the existing
// mesh path. The sniff only reads the textual header, no row data.
if(Threedim::PrimitiveCloud::ply_is_splat_shaped(fname))
{
const QString label = QFileInfo(QString::fromStdString(std::string{fname}))
.fileName();
loaded = Threedim::sceneStateFromMeshes(
std::move(meshes), std::move(buf), label.toStdString());
auto cloud = Threedim::PrimitiveCloud::parse_ply(fname);
if(cloud)
{
const QString label
= QFileInfo(QString::fromStdString(std::string{fname})).fileName();
loaded = Threedim::PrimitiveCloud::sceneStateFromCloud(
std::move(cloud), label.toStdString());
}
}
else
{
Threedim::float_vec buf;
auto meshes = Threedim::PlyFromFile(fname, buf);
if(!meshes.empty())
{
const QString label
= QFileInfo(QString::fromStdString(std::string{fname})).fileName();
loaded = Threedim::sceneStateFromMeshes(
std::move(meshes), std::move(buf), label.toStdString());
}
}
}
else if(hasSuffixCI(fname, "stl"))
Expand All @@ -210,6 +228,34 @@ AssetLoader::ins::asset_t::process(file_type tv)
std::move(meshes), std::move(buf), label.toStdString());
}
}
else if(hasSuffixCI(fname, "splat"))
{
// Antimatter15 binary .splat: 32 bytes/primitive, fixed schema.
auto cloud = Threedim::PrimitiveCloud::parse_splat_binary(tv.bytes);
if(cloud)
{
const QString label
= QFileInfo(QString::fromStdString(std::string{fname})).fileName();
loaded = Threedim::PrimitiveCloud::sceneStateFromCloud(
std::move(cloud), label.toStdString());
}
}
else if(hasSuffixCI(fname, "spz"))
{
// Niantic .spz v1-3: gzip-compressed column-grouped 3DGS data.
// Decoded via the vendored Niantic library (3rdparty/spz),
// transposed into the canonical 62-float row layout that the
// 3dgs.classic preset reads. v4 (NGSP-magic + ZSTD) returns
// nullptr — see 3rdparty/spz/CMakeLists.txt for the rationale.
auto cloud = Threedim::PrimitiveCloud::parse_spz(tv.bytes);
if(cloud)
{
const QString label
= QFileInfo(QString::fromStdString(std::string{fname})).fileName();
loaded = Threedim::PrimitiveCloud::sceneStateFromCloud(
std::move(cloud), label.toStdString());
}
}
else
{
// Built-ins all missed — consult the addon-registered parsers.
Expand All @@ -223,21 +269,33 @@ AssetLoader::ins::asset_t::process(file_type tv)
return {};

return [state = std::move(loaded)](AssetLoader& self) mutable {
self.m_raw_state = std::move(state);
self.m_parsed_state = std::move(state);
self.rebuild_format_state(); // m_parsed → m_overridden
self.m_cached_xform.valid = false; // force wrap rebuild
self.rebuild_wrapped_state();
};
}

void AssetLoader::rebuild_format_state()
{
m_cached_format_override = inputs.format_override.value;
m_overridden_state = Threedim::PrimitiveCloud::applyFormatOverride(
m_parsed_state, m_cached_format_override);
// The wrapped state derives from m_overridden_state and must be
// rebuilt whenever the override changes.
m_cached_xform.valid = false;
rebuild_wrapped_state();
}

void AssetLoader::rebuild_wrapped_state()
{
m_wrapped_state = Threedim::wrapSceneWithTransform(
m_raw_state, inputs, m_cached_xform, m_version_counter, m_xform_ref);
m_overridden_state, inputs, m_cached_xform, m_version_counter, m_xform_ref);
}

void AssetLoader::operator()()
{
if(!m_raw_state)
if(!m_parsed_state)
{
outputs.scene_out.scene.state = nullptr;
outputs.scene_out.dirty = 0;
Expand Down Expand Up @@ -298,6 +356,13 @@ void AssetLoader::release(score::gfx::RenderList& r)
if(raw_transform_slot.valid())
r.registry().free(raw_transform_slot);
m_xform_ref = {};
// Clear cached scene_state so the next operator()() rebuilds against
// the post-release registry. Producer-state-drift Option A — see
// matching comment in Light::release. m_parsed_state stays valid
// (parser output, no slot refs); only m_overridden_state and
// m_wrapped_state embed registry refs and need clearing.
m_overridden_state.reset();
m_wrapped_state.reset();
}

} // namespace Threedim
29 changes: 26 additions & 3 deletions src/plugins/score-plugin-threedim/Threedim/AssetLoader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,24 @@ class AssetLoader
halp_meta(
extensions,
"3D assets (*.fbx *.gltf *.glb *.obj *.ply *.stl *.off "
"*.splat *.spz "
"*.usd *.usda *.usdc *.usdz)");
static std::function<void(AssetLoader&)> process(file_type data);
} asset;

PositionControl position;
RotationControl rotation;
ScaleControl scale;

// Stamps every primitive_cloud_component emitted by this asset
// with `format_id = value` when non-empty. Empty falls back to the
// parser's autodetection (PLY column sniffing, .splat / .spz
// hardcoded). Used to route unrecognised PLY columns or addon-
// produced files through a FlattenedSceneFilterNode in mode 12.
struct format_override_t : halp::lineedit<"Format override (auto if empty)", "">
{
void update(AssetLoader& n) { n.rebuild_format_state(); }
} format_override;
} inputs;

struct outs
Expand All @@ -126,13 +137,25 @@ class AssetLoader
score::gfx::Edge* e);
void release(score::gfx::RenderList& r);

// Raw scene as parsed from the file — stable as long as the file doesn't
// change. Wrapped into m_wrapped_state by applying TRS controls.
std::shared_ptr<const ossia::scene_state> m_raw_state;
// Raw scene as parsed from the file — stable as long as the file
// doesn't change. The pipeline is:
// m_parsed_state (parser output, never mutated)
// ↓ applyFormatOverride(format_override.value)
// m_overridden_state (format_id rewrites applied, or = parsed)
// ↓ wrapSceneWithTransform(position/rotation/scale)
// m_wrapped_state (final, published downstream)
std::shared_ptr<const ossia::scene_state> m_parsed_state;
std::shared_ptr<const ossia::scene_state> m_overridden_state;
std::shared_ptr<const ossia::scene_state> m_wrapped_state;
std::string m_cached_format_override;
CachedTRS m_cached_xform;
int64_t m_version_counter{0};

// Re-runs applyFormatOverride from the parsed state. Triggered by the
// lineedit's update() callback when the user edits the override
// field; also called once after parsing.
void rebuild_format_state();

score::gfx::GpuResourceRegistry::Slot raw_transform_slot;
ossia::gpu_slot_ref m_xform_ref{};

Expand Down
Loading
Loading