impr: Extend the .write texture API to be more capable#2641
Conversation
|
pkg.pr.new packages benchmark commit |
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 15.70%) | ❔ Unknown |
|---|---|---|---|
| 0 | 300 | 22 | 1 |
import * as ... in PR vs import * as ... in target (did bundle size increase?):
| Test | tsdown |
|---|---|
| STATIC_common.ts | 66.33 kB ( |
| STATIC_allImports.ts | 300.78 kB ( |
| STATIC_tgpu.ts | 272.49 kB ( |
| tgpu_fn.ts | 272.50 kB ( |
| tgpu_init.ts | 272.50 kB ( |
| tgpu_lazy.ts | 272.50 kB ( |
| tgpu_slot.ts | 272.50 kB ( |
| tgpu_const.ts | 272.50 kB ( |
| tgpu_unroll.ts | 272.50 kB ( |
| tgpu_resolve.ts | 272.50 kB ( |
| tgpu_accessor.ts | 272.50 kB ( |
| tgpu_comptime.ts | 272.50 kB ( |
| tgpu_vertexFn.ts | 272.50 kB ( |
| tgpu_computeFn.ts | 272.50 kB ( |
| tgpu_fragmentFn.ts | 272.50 kB ( |
| tgpu_privateVar.ts | 272.50 kB ( |
| tgpu_vertexLayout.ts | 272.51 kB ( |
| tgpu_workgroupVar.ts | 272.51 kB ( |
| tgpu_initFromDevice.ts | 272.51 kB ( |
| tgpu_bindGroupLayout.ts | 272.51 kB ( |
| tgpu_mutableAccessor.ts | 272.51 kB ( |
| tgpu_resolveWithContext.ts | 272.51 kB ( |
| common_writeChannels.ts | 28.64 kB |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 263.99 kB ( |
| tgpu_initFromDevice.ts | 263.47 kB ( |
| tgpu_resolve.ts | 164.59 kB ( |
| tgpu_resolveWithContext.ts | 164.53 kB ( |
| tgpu_bindGroupLayout.ts | 68.97 kB ( |
| tgpu_mutableAccessor.ts | 65.97 kB ( |
| tgpu_accessor.ts | 65.96 kB ( |
| tgpu_privateVar.ts | 65.31 kB ( |
| tgpu_workgroupVar.ts | 65.30 kB ( |
| tgpu_const.ts | 64.55 kB ( |
| tgpu_fn.ts | 38.14 kB ( |
| tgpu_fragmentFn.ts | 38.14 kB ( |
| tgpu_vertexFn.ts | 37.96 kB ( |
| tgpu_computeFn.ts | 37.66 kB ( |
| tgpu_vertexLayout.ts | 26.79 kB ( |
| tgpu_comptime.ts | 14.48 kB ( |
| tgpu_unroll.ts | 1.66 kB ( |
| tgpu_slot.ts | 1.54 kB ( |
| tgpu_lazy.ts | 1.19 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.89, 1.67, 4.02, 5.70, 7.32, 11.18, 24.29, 25.14]
line [0.91, 1.84, 4.13, 6.24, 7.48, 10.87, 22.49, 24.81]
line [0.95, 1.98, 4.59, 6.70, 7.94, 11.70, 21.19, 23.80]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.31, 0.63, 0.69, 0.80, 1.26, 1.13, 1.23, 1.52]
line [0.28, 0.47, 0.63, 0.79, 1.06, 1.13, 1.39, 1.56]
line [0.32, 0.56, 0.71, 0.85, 1.14, 1.17, 1.48, 1.61]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.80, 2.15, 4.19, 6.59, 12.24, 26.24, 55.41, 112.48]
line [1.00, 2.13, 4.51, 6.74, 12.39, 25.84, 53.93, 110.92]
line [0.84, 2.06, 4.01, 6.20, 13.06, 26.32, 55.96, 115.23]
|
|
Your LLM provider API key was rejected. Rotate the key in your provider dashboard, then update the matching GitHub Actions secret. Update repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
There was a problem hiding this comment.
Pull request overview
This PR expands TypeGPU’s texture write API to support richer region/cropping options, explicit mismatch handling via fit, async Blob uploads (writeAsync), channel packing (common.writeChannels), and new clear/copy region capabilities—while shifting resampling/blitting work to cached render pipelines and making required usage flags explicit.
Changes:
- Extend
texture.write(...)to accept detailed destination/source region options and explicit mismatch behavior (fit: 'stretch' | 'clip'), and addtexture.writeAsync(blob, ...). - Add
common.writeChannels(...)to pack separate sources into individual RGBA channels using render-pass blits. - Rework/caches blit utilities (mipmap generation, resampling, channel blits), update mocks/tests, and update docs + examples to the new API rules (notably requiring
'render'usage for image uploads).
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/typegpu/tests/texture.test.ts | Updates/extends texture tests for new write/copy/clear semantics and cached blit behavior. |
| packages/typegpu/src/indexNamedExports.ts | Exposes new texture write-related public types. |
| packages/typegpu/src/core/texture/textureWrite.ts | Introduces write option/types + normalization/helpers for image/blob writes. |
| packages/typegpu/src/core/texture/textureUtils.ts | Adds cached pipelines, channel blits, staged image uploads, and updated mipmap/resample flows. |
| packages/typegpu/src/core/texture/texture.ts | Implements new write, writeAsync, region buffer writes, region copyFrom, and color clear overload. |
| packages/typegpu/src/common/writeChannels.ts | Adds common.writeChannels API for per-channel packing. |
| packages/typegpu/src/common/index.ts | Exports writeChannels from the common barrel. |
| packages/typegpu-testing-utility/src/webgpuGlobals.ts | Adds GPUColorWrite globals for tests. |
| packages/typegpu-testing-utility/src/index.ts | Re-exports mock types used by updated tests. |
| packages/typegpu-testing-utility/src/extendedIt.ts | Enhances GPU mock fixtures (viewport/scissor) and exports mock types. |
| apps/typegpu-docs/tests/individual-example-tests/utils/commonMocks.ts | Updates createImageBitmap mock to respect resize options. |
| apps/typegpu-docs/tests/individual-example-tests/jelly-slider.test.ts | Updates example test expectations after shader/code changes. |
| apps/typegpu-docs/src/examples/tests/texture-write/meta.json | Renames example title to “Texture Write”. |
| apps/typegpu-docs/src/examples/tests/texture-write/index.ts | New interactive docs example showcasing new write options + writeAsync. |
| apps/typegpu-docs/src/examples/tests/texture-write/index.html | Canvas host for the new texture-write example. |
| apps/typegpu-docs/src/examples/tests/texture-test/index.ts | Removes the older texture test example (replaced by newer ones). |
| apps/typegpu-docs/src/examples/tests/texture-mipmaps/meta.json | Adds new “Texture Mipmaps” example metadata. |
| apps/typegpu-docs/src/examples/tests/texture-mipmaps/index.ts | New docs example exercising mip generation + clears + raw writes. |
| apps/typegpu-docs/src/examples/tests/texture-mipmaps/index.html | Canvas host for texture-mipmaps example. |
| apps/typegpu-docs/src/examples/tests/texture-compose/meta.json | Adds new “Texture Compose” example metadata. |
| apps/typegpu-docs/src/examples/tests/texture-compose/index.ts | New docs example demonstrating channel packing + region copy + clear. |
| apps/typegpu-docs/src/examples/tests/texture-compose/index.html | Canvas host for texture-compose example. |
| apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts | Migrates background texture upload to writeAsync(blob, { fit: 'stretch' }). |
| apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts | Updates texture writes to use fit: 'stretch' to avoid mismatch throws. |
| apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts | Updates sprite texture write to use fit: 'stretch'. |
| apps/typegpu-docs/src/content/docs/apis/textures.mdx | Updates docs to new write/writeAsync/fit/writeChannels semantics and render-usage requirement. |
Comments suppressed due to low confidence (2)
packages/typegpu/src/core/texture/texture.ts:627
- Image writes accept origin/size/mipLevel, but #writeImages doesn’t validate that the target region fits within the selected mip’s bounds (or that mipLevel is in range). Out-of-bounds regions will fall through to WebGPU validation errors. Consider validating mipLevel and the target rect (origin + size) against the computed mip size/layer count before issuing copy/blit work.
for (const write of writes) {
const normalized = normalizeImageWrite(write);
if (!needsResize(normalized)) {
copyImageToTexture(this.#branch.device, this[$internal].unwrap(), normalized);
packages/typegpu/src/core/texture/texture.ts:661
- copyFrom(options) allows region copies between differently sized textures, but the computed copy size isn’t validated against the source/target mip bounds. When options.size is omitted (or origins are large), this can generate negative/oversized extents and defer failures to WebGPU validation. Add explicit source/target mipLevel and region bounds checks before encoding the copy.
const sourceMipLevel = options?.sourceMipLevel ?? 0;
const sourceOrigin = {
x: options?.sourceOrigin?.[0] ?? 0,
y: options?.sourceOrigin?.[1] ?? 0,
z: options?.sourceOrigin?.[2] ?? 0,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of commit ca5e3e6 ("review fix"), which completes writeChannels validation to match the behavior of imageWriteForLayer.
writeChannelsvalidates source-to-target size mismatches — the helper now computes a default target size viadefaultWriteSize(respectingmipLevelandorigin), throwing when the source and target dimensions differ and nofitmode is specified. This was previously silently resampled.- Error-path test for the missing-
fitcase — a new test confirms thatwriteChannelswith a 16×16 source on a 64×64 texture throws the expected mismatch error when nofitoption is provided.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
d7a9fc6 to
4189b73
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — incremental review of commit d7a9fc6 ("add proper 3d texture support"), which adds 3D texture write support, pipeline caching, and a new interactive docs example.
- 3D texture image writes —
copyImageToTexturestages through a 2D texture +copyTextureToTexturesince the WebGPU copy-image entry point only accepts 2D destinations. Resampled 3D writes render into individual depth slices viadepthSlicein the render pass attachment. - Pipeline caching — blit and channel-write render pipelines are now cached per device in
getBlitPipeline/getChannelPipeline, keyed by format, filterability, sample type, and channel mapping. - Blit improvements —
loadOpandviewportoptions allow resampled writes to target sub-regions without clearing untouched texels (coversFullMipLevelgates theclearvsloaddecision).depthSlicesupport enables per-slice rendering in 3D textures. - Mock refactoring — test mock factories (
createDeviceMock,createCommandEncoderMock,createRenderPassEncoderMock) extracted with exported types, andcopyTextureToTexture/depthSlicerecording added togpuCallTimeline. - New tests — covers 3D staging, resampling into depth slices, and mip-scaled depth layer validation.
- Docs — new
texture-write-3dexample with a volumetric ray-marching sandbox; browser-support caution forGPUCopyExternalImageSourcetypes.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Decide whether to attempt
3dtexture support or throw a clear error (supported)texture.write(image)now takes:origin,size,sourceOrigin,sourceSize,mipLevel,filter,flipY,premultipliedAlpha,colorSpaceimplicit resampling on size mismatch is gone, mismatches now throw unless you pass
fit: 'stretch'(scale) orfit: 'clip'(1:1 copy of the overlapping region)new
texture.writeAsync(blob)decodes a Blob and writes it, withfit: 'stretch'the resize happens during decode so there is no pipeline on our sidenew
common.writeChannels(texture, { r: { source, from }, ... })packs image sources into individual channels of one texture (lovely for pbr)new
texture.clear(color)overload, andcopyFrom/ raw data writes accept region + mip optionsimage writes now require the
'render'usage flag (previously browser level fail)sRGB stays sRGB during resampling, wide formats stage in
rgba16float, blit pipelines are cached per devicefit behavior matrix
Target region =
sizeoption, or mip size minus origin by default.fitfit: 'stretch'fit: 'clip'originset, nosizeorigin+sizesetsizesizeatoriginorigin, cut tomin(source, size)mipLevelsetwriteAsync(blob)writeChannels