Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: gradle/actions/setup-gradle@v4
- name: Build the library and the dev app
working-directory: apps/android-dev
run: ./gradlew :splatkit:assembleRelease :app:assembleRelease --no-daemon
run: ./gradlew :splatkit:assembleRelease :app:assembleRelease :splatkit:testDebugUnitTest --no-daemon
- name: Keep the AAR
uses: actions/upload-artifact@v4
with:
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/engine.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: splatkit-engine

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Configure
run: cmake -S packages/splatkit-engine -B build/splatkit-engine -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build/splatkit-engine --parallel
- name: Test
run: ctest --test-dir build/splatkit-engine --output-on-failure
22 changes: 22 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: splatkit-ios

on:
push:
branches: [main]
pull_request:

jobs:
# The Metal code and its tests build on macOS; the device library is the same source
# with the iOS toolchain flags, built here to keep it compiling.
test:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Configure (macOS, tests)
run: cmake -S packages/splatkit-ios -B build/ios-mac -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build/ios-mac --parallel
- name: Test
run: ctest --test-dir build/ios-mac --output-on-failure
- name: Build for iOS
run: scripts/build-ios.sh
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ jobs:
with:
packages: "ndk;27.1.12297006 cmake;3.22.1"
- name: clang-format and clang-tidy
run: scripts/lint-cpp.sh
run: ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.1.12297006" scripts/lint-cpp.sh
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# SplatKit agents

SDK integration or verification: read [the harness](docs/AGENT_HARNESS.md).
Implementation: follow [CONTRIBUTING.md](CONTRIBUTING.md); terminology lives in [CONTEXT.md](CONTEXT.md).
Keep documentation terse; link code/contracts instead of duplicating them.
Report skipped checks, approximation limits and emulator/physical-device provenance explicitly.
89 changes: 89 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SplatKit

An engine that walks Gaussian splat worlds on phones: a shared C++ core (formats, sorting, navigation, level of detail) and one renderer per platform.
This glossary is the vocabulary of the code, the docs and the conversation; it names concepts, not implementations.

## Language

### The world

**Splat**:
One Gaussian: a position, a covariance, a colour and an opacity.
_Avoid_: point, gaussian, particle

**Cloud**:
The splats of a world decoded into memory, structure of arrays, in the engine's own frame.
_Avoid_: point cloud, dataset, buffer

**World**:
What a host asks the engine to show: a source of splats plus, optionally, a collider. A world is one file today and a tileset tomorrow; the host does not know which.
_Avoid_: scene, model, asset, map

**Collider**:
The triangle mesh the walk camera collides with; the world's floor and walls for navigation only, never drawn.
_Avoid_: mesh, geometry, nav mesh

**Source**:
Where a world's bytes come from: a file, an asset, a content provider or a URL. Fetching a source puts its bytes on disk; it says nothing about how they are drawn.
_Avoid_: URI (that is the wire format of a source), download

### Drawing

**Frame**:
One presented image. The engine draws a frame only when something changed.

**Sort**:
Ordering splats by camera depth in the direction required by compositing.
_Avoid_: depth sort, z-order

**Cull**:
Rejecting splats outside the view or below the configured visibility threshold.

**Render scale**:
The size of the render target relative to the surface, 0.1 to 2. Below 1 the frame is upscaled, above 1 supersampled.
_Avoid_: resolution, resolution mode, quality (that is the preset)

**Preset**:
A named quality setting (low, medium, high, ultra) that fixes the render scale, the harmonics degree and the splat budget together.
_Avoid_: mode, profile, level

**Splat budget**:
Selection capacity; zero disables reduction.
_Avoid_: limit, cap, max splats

### Level of detail

**Node**:
One entry of the in-memory hierarchy over a cloud: a leaf is a splat of the file, an interior node is one splat standing in for its children.

**Tree**:
Offline/load-time hierarchy.
_Avoid_: LOD, octree (a tree of nodes is not an octree of tiles)

**Selection**:
Covering cut; capacity≠quality.

### Scale

**Tile**:
A cube of the world at one level, stored as its own spz file, with its splats in spatial order.
_Avoid_: chunk, cell, block, node (a node is inside a cloud, a tile is a cloud)

**Screen tile**:
A rectangular group of image pixels composited together; unrelated to a world's streaming tiles.

**Level**:
How coarse a tile is: level 0 is the file's splats, each level up stands in for the eight tiles below it with fewer, larger splats. Made offline, never on the phone.
_Avoid_: LOD, mip, layer

**Tileset**:
The index of a tiled world: every tile's bounds, level, file and children, plus the size of the smallest splat each stands in for.
_Avoid_: manifest, catalogue, tree

**Streaming**:
Loading and dropping tiles by what the camera can see while walking, so memory holds a neighbourhood, never the world.
_Avoid_: downloading (that is fetching a source), lazy loading

**Residency budget**:
The most tile bytes held in memory at once; what streaming evicts against.
_Avoid_: cache size, memory limit
21 changes: 19 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ adb logcat -s SplatKit

Debug builds load the Khronos validation layer; a pull request must leave it silent.

## Build and run the iOS dev app

Needs Xcode 26 with the iOS platform and the Metal toolchain, CMake, [xcodegen](https://github.com/yonaskolb/XcodeGen), an iPhone, and a `.spz` plus its collider `.glb` in `apps/ios-dev/SplatKitDev/Resources`.

```
scripts/build-ios.sh # the static libraries, into build/ios/lib
cd apps/ios-dev
xcodegen generate
xcodebuild -scheme SplatKitDev -configuration Release -destination "id=<device udid>" -allowProvisioningUpdates build
xcrun devicectl device install app --device <udid> build/Build/Products/Release-iphoneos/SplatKitDev.app
xcrun devicectl device process launch --device <udid> --console com.splatkit.devapp -- --gyro 0
```

The app reads its switches from the command line (`--world`, `--tileset`, `--collider`, `--residency`, `--scale`, `--pose`, `--benchmark`, `--capture`; see `LaunchArgs.swift`) and worlds from its Documents folder, which `devicectl device copy to` fills.
The Objective-C++ sources build with warnings as errors; `scripts/lint-cpp.sh` formats them.

## Lint the C++

```
Expand All @@ -51,8 +67,9 @@ The script configures both packages for the Android target and lints tests and t
## Code layout

`packages/splat-core` has no graphics dependency and is shared by every engine: formats, sorting, the level of detail tree, navigation, file mapping, the world loader and the visibility policy, each with tests.
`packages/splatkit-android` owns everything Vulkan and Android.
Its C++ is one `SplatEngine` (`cpp/engine`) that owns a `VulkanSplatRenderer` (surface, swapchain, pipelines, the world on the GPU), the camera, the sorter, a `Benchmark` and a `StatsPublisher`; `jni/` is the boundary to Kotlin and knows nothing else.
`packages/splatkit-engine` is the engine without a graphics API: one `SplatEngine` that owns the camera, the sorter or the streamer, a `Benchmark` and a `StatsPublisher`, and draws through the `SplatRenderer` interface; the GPU record layout (`GpuSplat` and the packing) lives here so every renderer uploads the same bytes.
`packages/splatkit-android` owns everything Vulkan and Android: `VulkanSplatRenderer` (surface, swapchain, pipelines, the world on the GPU) implements the interface, `AndroidEngine` wires it under the engine, and `jni/` is the boundary to Kotlin and knows nothing else.
`packages/splatkit-ios` owns everything Metal and iOS: `MetalSplatRenderer` implements the interface over a `CAMetalLayer`, `SKSplatEngine` is the Objective-C boundary to Swift, and `Sources/SplatKit` is the Swift layer (render thread, motion, `SplatMetalView`).
Its Kotlin has three layers: `com.splatkit` is the public API (`SplatSurfaceView` and the value types), `com.splatkit.engine` the JNI boundary and the render thread, `com.splatkit.input` touch and the gyroscope.
The engine does not know what is hosting it; [ADR 0013](docs/adr/0013-engine-modules.md) records the split.

Expand Down
26 changes: 26 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "SplatKit",
platforms: [.iOS(.v17)],
products: [.library(name: "SplatKit", targets: ["SplatKit"])],
targets: [
.binaryTarget(
name: "SplatKitCore",
url: "https://github.com/Xget7/splatkit-ios/releases/download/v0.1.0-alpha.2/SplatKitCore.xcframework.zip",
checksum: "477f26f4ac6c70a42c56209ae87450b2adefa5bf32cd54059148566944ec71e7"
),
.target(
name: "SplatKit",
dependencies: ["SplatKitCore"],
path: "packages/splatkit-ios/Sources/SplatKit",
linkerSettings: [
.linkedLibrary("c++"), .linkedLibrary("z"),
.linkedFramework("Metal"), .linkedFramework("QuartzCore"),
.linkedFramework("Foundation"), .linkedFramework("CoreGraphics"),
.linkedFramework("ImageIO"), .linkedFramework("UniformTypeIdentifiers"),
]
),
]
)
Loading
Loading