Skip to content

Commit 1c46e0f

Browse files
committed
feat(datasource/graphene) added ability to choose a mesh bounding box for graphene layer that requests a filtered manifest and additionally culls the mesh in the shader for a pixel perfect boundary
1 parent 41b9672 commit 1c46e0f

File tree

10 files changed

+475
-59
lines changed

10 files changed

+475
-59
lines changed

src/chunk_manager/backend.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,11 @@ function startChunkDownload(chunk: Chunk) {
450450
(error: any) => {
451451
if (chunk.downloadAbortController === downloadAbortController) {
452452
chunk.downloadAbortController = undefined;
453-
chunk.downloadFailed(error);
454-
console.log(`Error retrieving chunk ${chunk}: ${error}`);
453+
if (error === "retry") {
454+
} else {
455+
chunk.downloadFailed(error);
456+
console.log(`Error retrieving chunk ${chunk}: ${error}`);
457+
}
455458
}
456459
},
457460
);

src/chunk_manager/base.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ export interface ChunkSourceParametersConstructor<T> {
110110
RPC_ID: string;
111111
}
112112

113+
export interface ChunkSourceStateConstructor<T> {
114+
new (): T;
115+
}
116+
113117
export class LayerChunkProgressInfo {
114118
numVisibleChunksNeeded = 0;
115119
numVisibleChunksAvailable = 0;

src/chunk_manager/frontend.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import type {
1818
ChunkSourceParametersConstructor,
19+
ChunkSourceStateConstructor,
1920
LayerChunkProgressInfo,
2021
} from "#src/chunk_manager/base.js";
2122
import {
@@ -493,22 +494,30 @@ export interface ChunkSource {
493494

494495
export function WithParameters<
495496
Parameters,
497+
State,
496498
TBase extends ChunkSourceConstructor,
497499
>(
498500
Base: TBase,
499501
parametersConstructor: ChunkSourceParametersConstructor<Parameters>,
502+
state?: ChunkSourceStateConstructor<State>,
500503
) {
504+
state;
501505
type WithParametersOptions = InstanceType<TBase>["OPTIONS"] & {
502506
parameters: Parameters;
507+
state?: State;
503508
};
504509
@registerSharedObjectOwner(parametersConstructor.RPC_ID)
505510
class C extends Base {
506511
declare OPTIONS: WithParametersOptions;
507512
parameters: Parameters;
513+
state: State;
508514
constructor(...args: any[]) {
509515
super(...args);
510516
const options: WithParametersOptions = args[1];
511517
this.parameters = options.parameters;
518+
if (options.state) {
519+
this.state = options.state;
520+
}
512521
}
513522
initializeCounterpart(rpc: RPC, options: any) {
514523
options.parameters = this.parameters;

src/datasource/graphene/backend.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ import {
2727
getGrapheneFragmentKey,
2828
GRAPHENE_MESH_NEW_SEGMENT_RPC_ID,
2929
ChunkedGraphSourceParameters,
30-
MeshSourceParameters,
30+
MeshSourceParametersWithFocus,
3131
CHUNKED_GRAPH_LAYER_RPC_ID,
3232
CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID,
3333
RENDER_RATIO_LIMIT,
3434
isBaseSegmentId,
3535
parseGrapheneError,
3636
getHttpSource,
37+
startLayerForBBox,
3738
} from "#src/datasource/graphene/base.js";
3839
import { decodeManifestChunk } from "#src/datasource/precomputed/backend.js";
3940
import { WithSharedKvStoreContextCounterpart } from "#src/kvstore/backend.js";
@@ -98,7 +99,7 @@ function downloadFragmentWithSharding(
9899
function downloadFragment(
99100
fragmentKvStore: KvStoreWithPath,
100101
fragmentId: string,
101-
parameters: MeshSourceParameters,
102+
parameters: MeshSourceParametersWithFocus,
102103
signal: AbortSignal,
103104
): Promise<ReadResponse> {
104105
if (parameters.sharding) {
@@ -123,7 +124,7 @@ async function decodeDracoFragmentChunk(
123124
@registerSharedObject()
124125
export class GrapheneMeshSource extends WithParameters(
125126
WithSharedKvStoreContextCounterpart(MeshSource),
126-
MeshSourceParameters,
127+
MeshSourceParametersWithFocus,
127128
) {
128129
manifestRequestCount = new Map<string, number>();
129130
newSegments = new Uint64Set();
@@ -136,6 +137,13 @@ export class GrapheneMeshSource extends WithParameters(
136137
this.parameters.fragmentUrl,
137138
);
138139

140+
focusBoundingBox: SharedWatchableValue<Float32Array | undefined>;
141+
142+
constructor(rpc: RPC, options: MeshSourceParametersWithFocus) {
143+
super(rpc, options);
144+
this.focusBoundingBox = rpc.get(options.focusBoundingBox);
145+
}
146+
139147
addNewSegment(segment: bigint) {
140148
const { newSegments } = this;
141149
newSegments.add(segment);
@@ -146,12 +154,41 @@ export class GrapheneMeshSource extends WithParameters(
146154
}
147155

148156
async download(chunk: ManifestChunk, signal: AbortSignal) {
157+
const {
158+
focusBoundingBox: { value: focusBoundingBox },
159+
} = this;
160+
const { chunkSize, nBitsForLayerId } = this.parameters;
161+
162+
const unregister = this.registerDisposer(
163+
this.focusBoundingBox.changed.add(() => {
164+
chunk.downloadAbortController?.abort("retry");
165+
unregister();
166+
if (chunk.newRequestedState !== ChunkState.NEW) {
167+
// re-download manifest
168+
this.chunkManager.queueManager.updateChunkState(
169+
chunk,
170+
ChunkState.QUEUED,
171+
);
172+
}
173+
}),
174+
);
175+
149176
const { parameters, newSegments, manifestRequestCount } = this;
150-
if (isBaseSegmentId(chunk.objectId, parameters.nBitsForLayerId)) {
177+
if (isBaseSegmentId(chunk.objectId, nBitsForLayerId)) {
151178
return decodeManifestChunk(chunk, { fragments: [] });
152179
}
180+
153181
const { fetchOkImpl, baseUrl } = this.manifestHttpSource;
154-
const manifestPath = `/manifest/${chunk.objectId}:${parameters.lod}?verify=1&prepend_seg_ids=1`;
182+
let manifestPath = `/manifest/${chunk.objectId}:${parameters.lod}?verify=1&prepend_seg_ids=1`;
183+
if (focusBoundingBox) {
184+
const rank = focusBoundingBox.length / 2;
185+
const startLayer = startLayerForBBox(focusBoundingBox, chunkSize);
186+
const boundsStr = Array.from(
187+
new Array(rank),
188+
(_, i) => `${focusBoundingBox[i]}-${focusBoundingBox[i + rank]}`,
189+
).join("_");
190+
manifestPath += `&bounds=${boundsStr}&start_layer=${startLayer}`;
191+
}
155192
const response = await (
156193
await fetchOkImpl(baseUrl + manifestPath, { signal })
157194
).json();

src/datasource/graphene/base.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type {
2727
DataType,
2828
} from "#src/sliceview/base.js";
2929
import { makeSliceViewChunkSpecification } from "#src/sliceview/base.js";
30-
import type { mat4 } from "#src/util/geom.js";
30+
import type { mat4, vec3 } from "#src/util/geom.js";
3131
import type { FetchOk, HttpError } from "#src/util/http_request.js";
3232

3333
export const PYCG_APP_VERSION = 1;
@@ -59,10 +59,14 @@ export class MeshSourceParameters {
5959
lod: number;
6060
sharding: Array<ShardingParameters> | undefined;
6161
nBitsForLayerId: number;
62-
62+
chunkSize: vec3;
6363
static RPC_ID = "graphene/MeshSource";
6464
}
6565

66+
export class MeshSourceParametersWithFocus extends MeshSourceParameters {
67+
focusBoundingBox: number;
68+
}
69+
6670
export class MultiscaleMeshMetadata {
6771
transform: mat4;
6872
lodScaleMultiplier: number;
@@ -172,3 +176,17 @@ export function getHttpSource(
172176
}
173177
return { fetchOkImpl, baseUrl: joinBaseUrlAndPath(baseUrl, path) };
174178
}
179+
180+
export const startLayerForBBox = (
181+
focusBoundingBox: Float32Array<ArrayBufferLike>,
182+
chunkSize: vec3,
183+
) => {
184+
const rank = 3; // TODO need to change vec3 otherwise it doesn't make sense to make this a variable
185+
let minChunks = Number.POSITIVE_INFINITY;
186+
for (let i = 0; i < rank; i++) {
187+
const length = focusBoundingBox[i + rank] - focusBoundingBox[i];
188+
const numChunks = Math.ceil(length / chunkSize[i]);
189+
minChunks = Math.min(minChunks, numChunks);
190+
}
191+
return 2 + Math.max(0, Math.floor(Math.log2(minChunks / 1)));
192+
};

0 commit comments

Comments
 (0)