declare const shader = "\nconst MAX_STRENGTH = 65535f;\n\n// Workgroup size - X*Y*Z must be multiple of 32 for better performance\noverride workGroupSizeX = 1u;\noverride workGroupSizeY = 1u;\noverride workGroupSizeZ = 1u;\n\n// Compare the current voxel to neighbors using a 9x9x9 window\noverride windowSize = 9i;\n\nstruct Params {\n size: vec3u,\n iteration: u32,\n}\n\n// New structure to track bounds of modified voxels\nstruct Bounds {\n minX: atomic,\n minY: atomic,\n minZ: atomic,\n maxX: atomic,\n maxY: atomic,\n maxZ: atomic,\n}\n\n@group(0) @binding(0) var params: Params;\n@group(0) @binding(1) var volumePixelData: array;\n@group(0) @binding(2) var labelmap: array;\n@group(0) @binding(3) var strengthData: array;\n@group(0) @binding(4) var prevLabelmap: array;\n@group(0) @binding(5) var prevStrengthData: array;\n@group(0) @binding(6) var updatedVoxelsCounter: array>;\n@group(0) @binding(7) var modifiedBounds: Bounds;\n\nfn getPixelIndex(ijkPos: vec3u) -> u32 {\n let numPixelsPerSlice = params.size.x * params.size.y;\n return ijkPos.x + ijkPos.y * params.size.x + ijkPos.z * numPixelsPerSlice;\n}\n\nfn updateBounds(position: vec3i) {\n // Atomically update min bounds (use min operation)\n let oldMinX = atomicMin(&modifiedBounds.minX, position.x);\n let oldMinY = atomicMin(&modifiedBounds.minY, position.y);\n let oldMinZ = atomicMin(&modifiedBounds.minZ, position.z);\n\n // Atomically update max bounds (use max operation)\n let oldMaxX = atomicMax(&modifiedBounds.maxX, position.x);\n let oldMaxY = atomicMax(&modifiedBounds.maxY, position.y);\n let oldMaxZ = atomicMax(&modifiedBounds.maxZ, position.z);\n}\n\n@compute @workgroup_size(workGroupSizeX, workGroupSizeY, workGroupSizeZ)\nfn main(\n @builtin(global_invocation_id) globalId: vec3u,\n) {\n // Make sure it will not get out of bounds for volume with sizes that\n // are not multiple of workGroupSize\n if (\n globalId.x >= params.size.x ||\n globalId.y >= params.size.y ||\n globalId.z >= params.size.z\n ) {\n return;\n }\n\n // Initialize bounds for the first iteration\n if (params.iteration == 0 && globalId.x == 0 && globalId.y == 0 && globalId.z == 0) {\n // Initialize to opposite extremes to ensure any update will improve the bounds\n atomicStore(&modifiedBounds.minX, i32(params.size.x));\n atomicStore(&modifiedBounds.minY, i32(params.size.y));\n atomicStore(&modifiedBounds.minZ, i32(params.size.z));\n atomicStore(&modifiedBounds.maxX, -1);\n atomicStore(&modifiedBounds.maxY, -1);\n atomicStore(&modifiedBounds.maxZ, -1);\n }\n\n let currentCoord = vec3i(globalId);\n let currentPixelIndex = getPixelIndex(globalId);\n\n let numPixels = arrayLength(&volumePixelData);\n let currentPixelValue = volumePixelData[currentPixelIndex];\n\n if (params.iteration == 0) {\n // All non-zero initial labels are given maximum strength\n strengthData[currentPixelIndex] = select(MAX_STRENGTH, 0., labelmap[currentPixelIndex] == 0);\n\n // Update bounds for non-zero initial labels\n if (labelmap[currentPixelIndex] != 0) {\n updateBounds(currentCoord);\n }\n return;\n }\n\n // It should at least copy the values from previous state\n var newLabel = prevLabelmap[currentPixelIndex];\n var newStrength = prevStrengthData[currentPixelIndex];\n\n let window = i32(ceil(f32(windowSize - 1) * .5));\n let minWindow = -1i * window;\n let maxWindow = 1i * window;\n\n for (var k = minWindow; k <= maxWindow; k++) {\n for (var j = minWindow; j <= maxWindow; j++) {\n for (var i = minWindow; i <= maxWindow; i++) {\n // Skip current voxel\n if (i == 0 && j == 0 && k == 0) {\n continue;\n }\n\n let neighborCoord = currentCoord + vec3i(i, j, k);\n\n // Boundary conditions. Do not grow outside of the volume\n if (\n neighborCoord.x < 0i || neighborCoord.x >= i32(params.size.x) ||\n neighborCoord.y < 0i || neighborCoord.y >= i32(params.size.y) ||\n neighborCoord.z < 0i || neighborCoord.z >= i32(params.size.z)\n ) {\n continue;\n }\n\n let neighborIndex = getPixelIndex(vec3u(neighborCoord));\n let neighborPixelValue = volumePixelData[neighborIndex];\n let prevNeighborStrength = prevStrengthData[neighborIndex];\n let strengthCost = abs(neighborPixelValue - currentPixelValue);\n let takeoverStrength = prevNeighborStrength - strengthCost;\n\n if (takeoverStrength > newStrength) {\n newLabel = prevLabelmap[neighborIndex];\n newStrength = takeoverStrength;\n }\n }\n }\n }\n\n if (labelmap[currentPixelIndex] != newLabel) {\n atomicAdd(&updatedVoxelsCounter[params.iteration], 1u);\n\n // Update bounds for modified voxels\n updateBounds(currentCoord);\n }\n\n labelmap[currentPixelIndex] = newLabel;\n strengthData[currentPixelIndex] = newStrength;\n}\n"; export default shader;