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
14 changes: 14 additions & 0 deletions packages/splatkit-android/src/main/cpp/engine/AndroidEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <android/log.h>

#include "engine/TraceSection.h"
#include "splatkit/Log.h"

namespace splatkit {
Expand Down Expand Up @@ -51,9 +52,11 @@ void AndroidEngine::setEventSink(EventSink sink) {
}

void AndroidEngine::render(int64_t frameTimeNanos) {
const TraceSection section(trace::kFrame);
// Before the engine samples its stats, which a still scene would otherwise leave stale.
renderer_->collectCompletedFrames();
engine_->render(frameTimeNanos);
traceBenchmark();
if (awaitingWorldFrame_ && renderer_->hasCompletedWorldFrame()) {
awaitingWorldFrame_ = false;
// The frame may have finished during this render: read it, so stats read after the
Expand All @@ -64,4 +67,15 @@ void AndroidEngine::render(int64_t frameTimeNanos) {
}
}

// One async slice over the benchmark turn, so a trace can be cut to the measured window.
void AndroidEngine::traceBenchmark() {
const bool running = engine_->benchmarkRunning();
if (running == benchmarkTraced_) return;
benchmarkTraced_ = running;
if (running)
ATrace_beginAsyncSection(trace::kBenchmark, trace::kBenchmarkCookie);
else
ATrace_endAsyncSection(trace::kBenchmark, trace::kBenchmarkCookie);
}

} // namespace splatkit
2 changes: 2 additions & 0 deletions packages/splatkit-android/src/main/cpp/engine/AndroidEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ class AndroidEngine {

private:
AndroidEngine() = default;
void traceBenchmark();

EventSink eventSink_;
bool awaitingWorldFrame_ = false;
uint32_t worldFrameSplatCount_ = 0;
bool benchmarkTraced_ = false;

std::unique_ptr<VulkanContext> ctx_;
std::unique_ptr<VulkanFrameLoop> frameLoop_;
Expand Down
33 changes: 33 additions & 0 deletions packages/splatkit-android/src/main/cpp/engine/TraceSection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <android/trace.h>

#include <cstdint>

namespace splatkit {

// Slice names shown on the calling thread in Perfetto; scripts/perfetto_analysis.py matches them.
namespace trace {
inline constexpr char kFrame[] = "SplatKit frame";
inline constexpr char kWaitForFrameSlot[] = "SplatKit wait for frame slot";
inline constexpr char kEncodeCompute[] = "SplatKit encode compute";
inline constexpr char kSubmitPresent[] = "SplatKit submit and present";
inline constexpr char kUploadWorld[] = "SplatKit upload world";
inline constexpr char kUploadTile[] = "SplatKit upload tile";
inline constexpr char kDecodeWorld[] = "SplatKit decode world";
inline constexpr char kDecodeCollider[] = "SplatKit decode collider";
// Async slice on the process track; one benchmark runs at a time, so one cookie.
inline constexpr char kBenchmark[] = "SplatKit benchmark";
inline constexpr int32_t kBenchmarkCookie = 1;
} // namespace trace

// Scoped slice; costs one flag check while nothing records the app.
class TraceSection {
public:
explicit TraceSection(const char* name) { ATrace_beginSection(name); }
~TraceSection() { ATrace_endSection(); }
TraceSection(const TraceSection&) = delete;
TraceSection& operator=(const TraceSection&) = delete;
};

} // namespace splatkit
20 changes: 17 additions & 3 deletions packages/splatkit-android/src/main/cpp/jni/SplatKitJni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <string>

#include "engine/AndroidEngine.h"
#include "engine/TraceSection.h"
#include "jni/JniConversions.h"
#include "splatkit/Log.h"

Expand All @@ -22,6 +23,7 @@

namespace {

using splatkit::TraceSection;
using splatkit::jni::cameraPoseToFloats;
using splatkit::jni::cameraRequestFromDoubles;
using splatkit::jni::cameraStateToDoubles;
Expand All @@ -42,6 +44,7 @@ using splatkit::jni::statsToFloats;
using splatkit::jni::toJni;
using splatkit::jni::withBytes;
using splatkit::jni::withUtf8;
namespace trace = splatkit::trace;

// The handle Kotlin holds is the host's address; JNI has no other way to carry it.
splatkit::AndroidEngine* toHost(jlong handle) {
Expand Down Expand Up @@ -164,6 +167,7 @@ SPLATKIT_JNI(void, nativeLoadWorld)(JNIEnv* env, jobject, jlong handle, jbyteArr
auto* engine = toEngine(handle);
if (engine == nullptr) return;
withBytes(env, bytes, [engine](const std::uint8_t* data, std::size_t size) {
const TraceSection section(trace::kDecodeWorld);
engine->loadWorld(data, size);
});
}
Expand All @@ -172,26 +176,36 @@ SPLATKIT_JNI(void, nativeLoadCollider)(JNIEnv* env, jobject, jlong handle, jbyte
auto* engine = toEngine(handle);
if (engine == nullptr) return;
withBytes(env, bytes, [engine](const std::uint8_t* data, std::size_t size) {
const TraceSection section(trace::kDecodeCollider);
engine->loadCollider(data, size);
});
}

SPLATKIT_JNI(void, nativeLoadWorldFile)(JNIEnv* env, jobject, jlong handle, jstring path) {
auto* engine = toEngine(handle);
if (engine == nullptr) return;
withUtf8(env, path, [engine](const std::string& p) { engine->loadWorldFile(p); });
withUtf8(env, path, [engine](const std::string& p) {
const TraceSection section(trace::kDecodeWorld);
engine->loadWorldFile(p);
});
}

SPLATKIT_JNI(void, nativeLoadTiledWorldFile)(JNIEnv* env, jobject, jlong handle, jstring path) {
auto* engine = toEngine(handle);
if (engine == nullptr) return;
withUtf8(env, path, [engine](const std::string& p) { engine->loadTiledWorldFile(p); });
withUtf8(env, path, [engine](const std::string& p) {
const TraceSection section(trace::kDecodeWorld);
engine->loadTiledWorldFile(p);
});
}

SPLATKIT_JNI(void, nativeLoadColliderFile)(JNIEnv* env, jobject, jlong handle, jstring path) {
auto* engine = toEngine(handle);
if (engine == nullptr) return;
withUtf8(env, path, [engine](const std::string& p) { engine->loadColliderFile(p); });
withUtf8(env, path, [engine](const std::string& p) {
const TraceSection section(trace::kDecodeCollider);
engine->loadColliderFile(p);
});
}

// Camera and input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include <limits>

#include "engine/TraceSection.h"
#include "rendering/vulkan/sort/VulkanRadixSort.h"

#include "splatkit/Log.h"
Expand Down Expand Up @@ -118,6 +119,7 @@ std::optional<GpuWorldInfo> VulkanSplatRenderer::world() const {
}

bool VulkanSplatRenderer::uploadWorld(const splat::SplatCloud& cloud, int maxShDegree) {
const TraceSection section(trace::kUploadWorld);
if (!splats_ || cloud.count() > std::numeric_limits<uint32_t>::max()) return false;
ctx_.waitIdle();
auto compute = VulkanFrameCompute::create(ctx_, static_cast<uint32_t>(cloud.count()));
Expand Down Expand Up @@ -148,6 +150,7 @@ bool VulkanSplatRenderer::selectsLodOnGpu() const {

bool VulkanSplatRenderer::uploadLodWorld(const splat::LodTree& tree, int maxShDegree,
uint32_t budget) {
const TraceSection section(trace::kUploadWorld);
if (!splats_ || tree.nodeCount() > std::numeric_limits<uint32_t>::max()) return false;
ctx_.waitIdle();
auto compute =
Expand All @@ -167,6 +170,7 @@ bool VulkanSplatRenderer::uploadLodWorld(const splat::LodTree& tree, int maxShDe
}

bool VulkanSplatRenderer::createSlab(uint32_t capacity, int shDegree) {
const TraceSection section(trace::kUploadWorld);
if (!splats_) return false;
ctx_.waitIdle();
auto compute = VulkanFrameCompute::create(ctx_, capacity);
Expand All @@ -186,6 +190,7 @@ bool VulkanSplatRenderer::createSlab(uint32_t capacity, int shDegree) {
}

bool VulkanSplatRenderer::uploadTile(uint32_t offset, const splat::SplatCloud& cloud) {
const TraceSection section(trace::kUploadTile);
if (!splats_ || !world_) return false;
return world_->uploadTile(offset, cloud);
}
Expand All @@ -194,7 +199,10 @@ bool VulkanSplatRenderer::draw(const Frame& frame) {
if (!ready()) return false;
uint32_t imageIndex = 0;
VkCommandBuffer cmd = VK_NULL_HANDLE;
VulkanFrameLoop::Status status = frameLoop_.beginFrame(surface_.swapchain(), imageIndex, cmd);
VulkanFrameLoop::Status status = [&] {
const TraceSection section(trace::kWaitForFrameSlot);
return frameLoop_.beginFrame(surface_.swapchain(), imageIndex, cmd);
}();
if (status == VulkanFrameLoop::Status::swapchainOutOfDate) {
rebuildSurface(status);
return false;
Expand All @@ -209,7 +217,10 @@ bool VulkanSplatRenderer::draw(const Frame& frame) {
if (world_ && compute_ && frame.orderSource == OrderSource::gpu) {
const VkBuffer camera =
camera_->update(slot, frame.view, frame.proj, frame.cameraPosition, extent, outputLinear);
gpuDraw = compute_->encode(cmd, slot, camera, *world_->splats, frame);
{
const TraceSection section(trace::kEncodeCompute);
gpuDraw = compute_->encode(cmd, slot, camera, *world_->splats, frame);
}
if (gpuDraw)
splats_->bindOrder(slot, gpuDraw->order, gpuDraw->capacity);
else
Expand Down Expand Up @@ -253,7 +264,10 @@ bool VulkanSplatRenderer::draw(const Frame& frame) {
surface_.blitIfScaled(cmd, imageIndex);

const uint64_t submittedBefore = frameLoop_.lastSubmission();
status = frameLoop_.endFrame(surface_.swapchain(), imageIndex);
{
const TraceSection section(trace::kSubmitPresent);
status = frameLoop_.endFrame(surface_.swapchain(), imageIndex);
}
if (gpuDraw && frameLoop_.lastSubmission() != submittedBefore)
compute_->submitted(slot, frameLoop_.lastSubmission());
const bool drewWorld =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ class SplatEngine {
// Runs a reproducible capture: gyroscope off, a fixed pose, one full yaw turn over
// `seconds`, then logs the frame time distribution. Waits for a world if none is up.
void startBenchmark(float seconds);
// Render thread: true from the first recorded frame of the turn until its summary.
bool benchmarkRunning() const { return benchmark_.running(); }
const std::string& gpuDescription() const { return renderer_->deviceDescription(); }

private:
Expand Down
Loading