Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement derived content caching #990

Draft
wants to merge 9 commits into
base: ue4-main
Choose a base branch
from
Prev Previous commit
Next Next commit
proof of concept of derived content caching
  • Loading branch information
nithinp7 committed Oct 23, 2022
commit 6b5c5e7445040d689d1f5f2092f3171fe54a4420
2 changes: 2 additions & 0 deletions Source/CesiumRuntime/CesiumRuntime.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 80,10 @@ public CesiumRuntime(ReadOnlyTargetRules Target) : base(Target)
"CesiumGeometry",
"CesiumGeospatial",
"CesiumGltfReader",
"CesiumGltfWriter",
"CesiumGltf",
"CesiumJsonReader",
"CesiumJsonWriter",
"CesiumUtility",
"draco",
"ktx_read",
Expand Down
304 changes: 294 additions & 10 deletions Source/CesiumRuntime/Private/Cesium3DTileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 25,8 @@
#include "CesiumGltf/Ktx2TranscodeTargets.h"
#include "CesiumGltfComponent.h"
#include "CesiumGltfPrimitiveComponent.h"
#include "CesiumGltfReader/GltfReader.h"
#include "CesiumGltfWriter/GltfWriter.h"
#include "CesiumLifetime.h"
#include "CesiumRasterOverlay.h"
#include "CesiumRuntime.h"
Expand Down Expand Up @@ -611,6 613,258 @@ void ACesium3DTileset::NotifyHit(
// std::cout << "Hit face index 2: " << detailedHit.FaceIndex << std::endl;
}

namespace {
/*
Format Outline:
1. CachedGltfHeader
2. glTF JSON
3. CachedBufferDescription 1, 2, 3...
4. CachedImageDescription 1, 2, 3...
5. Binary Chunk with decoded buffers / images
*/
// TODO: NOT PERMANENT
struct CachedGltfHeader {
unsigned char magic[4];
uint32_t version;
uint32_t gltfJsonSize;
uint32_t cachedBuffersCount;
uint32_t cachedImagesCount;
};

struct CachedBufferDescription {
uint32_t byteOffset;
uint32_t byteSize;
};

struct CachedImageDescription {
uint32_t width;
uint32_t height;
uint32_t channels;
uint32_t bytesPerChannel;
uint32_t mipCount;
uint32_t byteOffset;
uint32_t byteSize;
};

// Convenient macro for runtime "asserting" - fails the function otherwise.
// Similar to an error monad.
#define PROCEED_IF(stmt) \
if (!(stmt)) { \
return std::nullopt; \
}
std::optional<CesiumGltf::Model>
deserializeGltf(const gsl::span<const std::byte>& cachedGltf) {

TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::DeserializeGltf)

PROCEED_IF(cachedGltf.size() >= sizeof(CachedGltfHeader))

size_t readPos = 0;

const CachedGltfHeader& header =
*reinterpret_cast<const CachedGltfHeader*>(&cachedGltf[readPos]);
readPos = sizeof(CachedGltfHeader);

// TODO: look for a more elegant magic check!
PROCEED_IF(header.magic[0] == 'C')
PROCEED_IF(header.magic[1] == '4')
PROCEED_IF(header.magic[2] == 'U')
PROCEED_IF(header.magic[3] == 'E')
PROCEED_IF(header.version == 1)

PROCEED_IF(cachedGltf.size() >= readPos header.gltfJsonSize);

gsl::span<const std::byte> gltfJsonBytes(
&cachedGltf[readPos],
header.gltfJsonSize);
readPos = header.gltfJsonSize;

CesiumGltfReader::GltfReader reader;
CesiumGltfReader::GltfReaderResult gltfJsonResult =
reader.readGltf(gltfJsonBytes);

PROCEED_IF(gltfJsonResult.errors.empty() && gltfJsonResult.model)
PROCEED_IF(
gltfJsonResult.model->buffers.size() == header.cachedBuffersCount &&
gltfJsonResult.model->images.size() == header.cachedImagesCount)

size_t bufferDescriptionsSize =
header.cachedBuffersCount * sizeof(CachedBufferDescription)
header.cachedImagesCount * sizeof(CachedImageDescription);

PROCEED_IF(cachedGltf.size() >= readPos bufferDescriptionsSize);

for (CesiumGltf::Buffer& buffer : gltfJsonResult.model->buffers) {
const CachedBufferDescription& description =
*reinterpret_cast<const CachedBufferDescription*>(&cachedGltf[readPos]);
readPos = sizeof(CachedBufferDescription);

PROCEED_IF(
cachedGltf.size() >= description.byteOffset description.byteSize)
buffer.cesium.data.resize(description.byteSize);
std::memcpy(
buffer.cesium.data.data(),
&cachedGltf[description.byteOffset],
description.byteSize);
}

for (CesiumGltf::Image& image : gltfJsonResult.model->images) {
const CachedImageDescription& description =
*reinterpret_cast<const CachedImageDescription*>(&cachedGltf[readPos]);
readPos = sizeof(CachedImageDescription);

PROCEED_IF(
cachedGltf.size() >= description.byteOffset description.byteSize)
image.cesium.pixelData.resize(description.byteSize);
std::memcpy(
image.cesium.pixelData.data(),
&cachedGltf[description.byteOffset],
description.byteSize);

image.cesium.width = static_cast<int32_t>(description.width);
image.cesium.height = static_cast<int32_t>(description.height);
image.cesium.channels = static_cast<int32_t>(description.channels);
image.cesium.bytesPerChannel =
static_cast<int32_t>(description.bytesPerChannel);

image.cesium.mipPositions.resize(description.mipCount);

size_t mipByteOffset = 0;

// A mip count of 0 indicates there is only one mip and it is within the
// pixelData.
if (description.mipCount != 0) {
CesiumGltf::ImageCesiumMipPosition& mip0 = image.cesium.mipPositions[0];
mip0.byteOffset = mipByteOffset;
mip0.byteSize = description.width * description.height *
description.channels * description.bytesPerChannel;

mipByteOffset = mip0.byteSize;
}

for (uint32_t mipIndex = 1; mipIndex < description.mipCount; mipIndex) {
uint32_t mipWidth = description.width >> mipIndex;
uint32_t mipHeight = description.height >> mipIndex;

if (mipWidth < 1) {
mipWidth = 1;
}

if (mipHeight < 1) {
mipHeight = 1;
}

CesiumGltf::ImageCesiumMipPosition& mip =
image.cesium.mipPositions[mipIndex];
mip.byteOffset = mipByteOffset;
mip.byteSize = mipWidth * mipHeight * description.channels *
description.bytesPerChannel;

mipByteOffset = mip.byteSize;
}
}

return std::move(gltfJsonResult.model);
}
#undef PROCEED_IF

std::vector<std::byte> serializeGltf(const CesiumGltf::Model& model) {
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::serializeGltf)

uint32_t bufferCount = static_cast<uint32_t>(model.buffers.size());
uint32_t imageCount = static_cast<uint32_t>(model.images.size());

CesiumGltfWriter::GltfWriter writer;
CesiumGltfWriter::GltfWriterResult gltfJsonResult = writer.writeGltf(model);

if (!gltfJsonResult.errors.empty()) {
return {};
}

size_t gltfJsonSize = gltfJsonResult.gltfBytes.size();

uint32_t binaryChunkSize = 0;
for (uint32_t i = 0; i < bufferCount; i) {
binaryChunkSize = model.buffers[i].cesium.data.size();
}

for (uint32_t i = 0; i < imageCount; i) {
binaryChunkSize = model.images[i].cesium.pixelData.size();
}

// TODO: alignment?
size_t binaryChunkOffset = sizeof(CachedGltfHeader) gltfJsonSize
bufferCount * sizeof(CachedBufferDescription)
imageCount * sizeof(CachedImageDescription);
size_t totalAllocation = binaryChunkOffset binaryChunkSize;

std::vector<std::byte> result(totalAllocation);
std::byte* pWritePos = result.data();

CachedGltfHeader& header = *reinterpret_cast<CachedGltfHeader*>(pWritePos);
header.magic[0] = 'C';
header.magic[1] = '4';
header.magic[2] = 'U';
header.magic[3] = 'E';
header.version = 1;
header.gltfJsonSize = static_cast<uint32_t>(gltfJsonSize);
header.cachedBuffersCount = bufferCount;
header.cachedImagesCount = imageCount;
pWritePos = sizeof(CachedGltfHeader);

// Copy gltf json
std::memcpy(pWritePos, gltfJsonResult.gltfBytes.data(), gltfJsonSize);
pWritePos = gltfJsonSize;

size_t binaryChunkWritePos = binaryChunkOffset;

for (const CesiumGltf::Buffer& buffer : model.buffers) {
CachedBufferDescription& description =
*reinterpret_cast<CachedBufferDescription*>(pWritePos);
description.byteOffset = static_cast<uint32_t>(binaryChunkWritePos);
description.byteSize = static_cast<uint32_t>(buffer.cesium.data.size());
pWritePos = sizeof(CachedBufferDescription);

std::memcpy(
&result[binaryChunkWritePos],
buffer.cesium.data.data(),
description.byteSize);
binaryChunkWritePos = description.byteSize;
}

for (const CesiumGltf::Image& image : model.images) {
CachedImageDescription& description =
*reinterpret_cast<CachedImageDescription*>(pWritePos);
description.width = static_cast<uint32_t>(image.cesium.width);
description.height = static_cast<uint32_t>(image.cesium.height);
description.channels = static_cast<uint32_t>(image.cesium.channels);
description.bytesPerChannel =
static_cast<uint32_t>(image.cesium.bytesPerChannel);
description.mipCount =
static_cast<uint32_t>(image.cesium.mipPositions.size());
description.byteOffset = binaryChunkWritePos;
description.byteSize = static_cast<uint32_t>(image.cesium.pixelData.size());
pWritePos = sizeof(CachedImageDescription);

std::memcpy(
&result[binaryChunkWritePos],
image.cesium.pixelData.data(),
description.byteSize);
binaryChunkWritePos = description.byteSize;
}

// The description and json writing should end at the start of the binary
// chunk.
check(pWritePos == &result[binaryChunkOffset]);

// The written binary chunk should end at the expected at the very end of the
// allocation.
check(binaryChunkWritePos == totalAllocation);

return result;
}
} // namespace

class UnrealResourcePreparer
: public Cesium3DTilesSelection::IPrepareRendererResources {
public:
Expand All @@ -624,21 878,49 @@ class UnrealResourcePreparer
#endif
{
}

virtual CesiumAsync::Future<
Cesium3DTilesSelection::TileLoadResultAndRenderResources>
virtual CesiumAsync::Future<Cesium3DTilesSelection::ClientTileLoadResult>
prepareInLoadThread(
const CesiumAsync::AsyncSystem& asyncSystem,
Cesium3DTilesSelection::TileLoadResult&& tileLoadResult,
const glm::dmat4& transform,
const std::any& rendererOptions) override {
CesiumGltf::Model* pModel =
std::get_if<CesiumGltf::Model>(&tileLoadResult.contentKind);
if (!pModel)
return asyncSystem.createResolvedFuture(
Cesium3DTilesSelection::TileLoadResultAndRenderResources{
std::move(tileLoadResult),
nullptr});
std::vector<std::byte> derivedBufferToCache;

if (pModel) {
// Freshly loaded model, serialize it into cache.
derivedBufferToCache = serializeGltf(*pModel);
} else {
if (std::get_if<Cesium3DTilesSelection::TileCachedRenderContent>(
&tileLoadResult.contentKind)) {
// Custom serialized data was found in the cache.
const CesiumAsync::IAssetResponse* pResponse =
tileLoadResult.pCompletedRequest->response();
if (pResponse) {
std::optional<CesiumGltf::Model> cachedGltf =
deserializeGltf(pResponse->clientData());

if (cachedGltf) {
tileLoadResult.contentKind = std::move(*cachedGltf);
pModel =
std::get_if<CesiumGltf::Model>(&tileLoadResult.contentKind);
} else {
UE_LOG(LogCesium, Warning, TEXT("Could not parse cached glTF"));
}
}
}

if (!pModel) {
// Write back the original response data.
return asyncSystem.createResolvedFuture(
Cesium3DTilesSelection::ClientTileLoadResult{
std::move(tileLoadResult),
nullptr,
true,
{}});
}
}

CreateGltfOptions::CreateModelOptions options;
options.pModel = pModel;
Expand All @@ -654,9 936,11 @@ class UnrealResourcePreparer
TUniquePtr<UCesiumGltfComponent::HalfConstructed> pHalf =
UCesiumGltfComponent::CreateOffGameThread(transform, options);
return asyncSystem.createResolvedFuture(
Cesium3DTilesSelection::TileLoadResultAndRenderResources{
Cesium3DTilesSelection::ClientTileLoadResult{
std::move(tileLoadResult),
pHalf.Release()});
pHalf.Release(),
false,
std::move(derivedBufferToCache)});
}

virtual void* prepareInMainThread(
Expand Down
3 changes: 2 additions & 1 deletion Source/CesiumRuntime/Private/UnrealAssetAccessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 122,8 @@ CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
UnrealAssetAccessor::get(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& url,
const std::vector<CesiumAsync::IAssetAccessor::THeader>& headers) {
const std::vector<CesiumAsync::IAssetAccessor::THeader>& headers,
bool /*writeThrough*/) {

CESIUM_TRACE_BEGIN_IN_TRACK("requestAsset");

Expand Down
4 changes: 2 additions & 2 deletions Source/CesiumRuntime/Public/UnrealAssetAccessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 16,8 @@ class CESIUMRUNTIME_API UnrealAssetAccessor
virtual CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
get(const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& url,
const std::vector<CesiumAsync::IAssetAccessor::THeader>& headers)
override;
const std::vector<CesiumAsync::IAssetAccessor::THeader>& headers,
bool writeThrough) override;

virtual CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
request(
Expand Down
2 changes: 1 addition & 1 deletion extern/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 79,7 @@ if (CESIUM_USE_UNREAL_TRACING)
list(APPEND CESIUM_EXTRA_INCLUDES "${UNREAL_ENGINE_DIR}/Engine/Source/Runtime/TraceLog/Public")
list(APPEND CESIUM_EXTRA_INCLUDES "${UNREAL_ENGINE_DIR}/Engine/Source/Runtime/Core/Public")
# Change this line depending on where your project's PCH is.
list(APPEND CESIUM_EXTRA_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../Intermediate/Build/Win64/UnrealEditor/Development/CesiumRuntime")
list(APPEND CESIUM_EXTRA_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../Intermediate/Build/Win64/UE4Editor/Development/CesiumRuntime")
# Let Cesium Native know that we are tracing and overriding the default tracing macros.
add_compile_definitions(CESIUM_TRACING_ENABLED)
add_compile_definitions(CESIUM_OVERRIDE_TRACING)
Expand Down
2 changes: 1 addition & 1 deletion extern/cesium-native
Submodule cesium-native updated 31 files
2 −1 .vscode/settings.json
2 −0 Cesium3DTilesSelection/include/Cesium3DTilesSelection/IPrepareRendererResources.h
5 −0 Cesium3DTilesSelection/include/Cesium3DTilesSelection/RasterOverlay.h
24 −19 Cesium3DTilesSelection/include/Cesium3DTilesSelection/TileContentCache.h
9 −10 Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetContentLoader.h
0 −11 Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetExternals.h
0 −47 Cesium3DTilesSelection/src/CachedTileContentAccessor.cpp
9 −6 Cesium3DTilesSelection/src/ImplicitOctreeLoader.cpp
9 −6 Cesium3DTilesSelection/src/ImplicitQuadtreeLoader.cpp
58 −0 Cesium3DTilesSelection/src/TileContentCache.cpp
2 −3 Cesium3DTilesSelection/src/TileContentLoadInfo.cpp
3 −4 Cesium3DTilesSelection/src/TileContentLoadInfo.h
2 −3 Cesium3DTilesSelection/src/TilesetContentLoader.cpp
51 −14 Cesium3DTilesSelection/src/TilesetContentManager.cpp
4 −0 Cesium3DTilesSelection/src/TilesetContentManager.h
76 −74 Cesium3DTilesSelection/src/TilesetJsonLoader.cpp
92 −2 Cesium3DTilesSelection/test/SimpleAssetAccessor.h
12 −2 Cesium3DTilesSelection/test/SimpleAssetResponse.h
18 −1 Cesium3DTilesSelection/test/SimplePrepareRendererResource.h
7 −4 Cesium3DTilesSelection/test/TestImplicitOctreeLoader.cpp
7 −4 Cesium3DTilesSelection/test/TestImplicitQuadtreeLoader.cpp
0 −1 Cesium3DTilesSelection/test/TestLayerJsonTerrainLoader.cpp
342 −0 Cesium3DTilesSelection/test/TestTileContentCache.cpp
0 −3 Cesium3DTilesSelection/test/TestTilesetContentManager.cpp
9 −4 Cesium3DTilesSelection/test/TestTilesetJsonLoader.cpp
5 −5 Cesium3DTilesSelection/test/TestTilesetSelectionAlgorithm.cpp
9 −69 CesiumAsync/include/CesiumAsync/CachingAssetAccessor.h
24 −1 CesiumAsync/include/CesiumAsync/IAssetAccessor.h
8 −0 CesiumAsync/include/CesiumAsync/IAssetResponse.h
53 −45 CesiumAsync/src/CachingAssetAccessor.cpp
2 −1 CesiumAsync/test/TestCacheAssetAccessor.cpp