(shader, x, y = 1, z = 1)
| 4136 | } |
| 4137 | |
| 4138 | compute(shader, x, y = 1, z = 1) { |
| 4139 | if (shader.shaderType !== 'compute') { |
| 4140 | throw new Error('compute() can only be called with a compute shader'); |
| 4141 | } |
| 4142 | |
| 4143 | this._finishActiveRenderPass(); |
| 4144 | |
| 4145 | // Ensure shader is initialized and finalized |
| 4146 | if (!shader._compiled) { |
| 4147 | shader.init(); |
| 4148 | } |
| 4149 | |
| 4150 | // Set default uniforms |
| 4151 | shader.setDefaultUniforms(); |
| 4152 | shader.setUniform('uTotalCount', [x, y, z]); |
| 4153 | |
| 4154 | // Calculate optimal workgroup size (8x8x1 = 64 threads per workgroup) |
| 4155 | const WORKGROUP_SIZE_X = 8; |
| 4156 | const WORKGROUP_SIZE_Y = 8; |
| 4157 | const WORKGROUP_SIZE_Z = 1; |
| 4158 | |
| 4159 | // auto spreading: if any dimension is too large or for performance optimization, |
| 4160 | // spread total iteration count across dimensions |
| 4161 | const totalIterations = x * y * z; |
| 4162 | const MAX_THREADS_PER_DIM = 65535 * 8; |
| 4163 | |
| 4164 | let px = x; |
| 4165 | let py = y; |
| 4166 | let pz = z; |
| 4167 | |
| 4168 | // we spread if we exceed GPU limits OR if it involves a large 1D dispatch |
| 4169 | const exceedsLimits = x > MAX_THREADS_PER_DIM || y > MAX_THREADS_PER_DIM || z > MAX_THREADS_PER_DIM; |
| 4170 | const isLarge1D = totalIterations > 1024 && y === 1 && z === 1; |
| 4171 | |
| 4172 | if (exceedsLimits || isLarge1D) { |
| 4173 | // Always use 2D square spreading (√N × √N). |
| 4174 | // Benchmarks showed 2D square equals or outperforms 3D cube at every |
| 4175 | // scale tested, with simpler index reconstruction in the shader. |
| 4176 | px = Math.ceil(Math.sqrt(totalIterations)); |
| 4177 | py = Math.ceil(totalIterations / px); |
| 4178 | pz = 1; |
| 4179 | } |
| 4180 | |
| 4181 | shader.setUniform('uPhysicalCount', [px, py, pz]); |
| 4182 | |
| 4183 | const workgroupCountX = Math.ceil(px / WORKGROUP_SIZE_X); |
| 4184 | const workgroupCountY = Math.ceil(py / WORKGROUP_SIZE_Y); |
| 4185 | const workgroupCountZ = Math.ceil(pz / WORKGROUP_SIZE_Z); |
| 4186 | |
| 4187 | const commandEncoder = this.device.createCommandEncoder(); |
| 4188 | const passEncoder = commandEncoder.beginComputePass(); |
| 4189 | this.setupShaderBindGroups(shader, passEncoder, { |
| 4190 | compute: true, |
| 4191 | workgroupSize: [WORKGROUP_SIZE_X, WORKGROUP_SIZE_Y, WORKGROUP_SIZE_Z], |
| 4192 | }); |
| 4193 | |
| 4194 | // Dispatch compute workgroups |
| 4195 | passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); |
no test coverage detected