Skip to content

impr: Extend the .write texture API to be more capable#2641

Open
reczkok wants to merge 25 commits into
mainfrom
impr/more-capable-texture-write
Open

impr: Extend the .write texture API to be more capable#2641
reczkok wants to merge 25 commits into
mainfrom
impr/more-capable-texture-write

Conversation

@reczkok

@reczkok reczkok commented Jun 22, 2026

Copy link
Copy Markdown
Contributor
  • Decide whether to attempt 3d texture support or throw a clear error (supported)

  • texture.write(image) now takes: origin, size, sourceOrigin, sourceSize, mipLevel, filter, flipY, premultipliedAlpha, colorSpace

  • implicit resampling on size mismatch is gone, mismatches now throw unless you pass fit: 'stretch' (scale) or fit: 'clip' (1:1 copy of the overlapping region)

  • new texture.writeAsync(blob) decodes a Blob and writes it, with fit: 'stretch' the resize happens during decode so there is no pipeline on our side

  • new common.writeChannels(texture, { r: { source, from }, ... }) packs image sources into individual channels of one texture (lovely for pbr)

  • new texture.clear(color) overload, and copyFrom / raw data writes accept region + mip options

  • image 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 device

fit behavior matrix

Target region = size option, or mip size minus origin by default.

Case no fit fit: 'stretch' fit: 'clip'
source == target direct copy direct copy (no-op) direct copy (no-op)
source smaller throws blit, source scaled up to fill target patch copy at origin, rest untouched
source larger throws blit, source scaled down to fit target top-left part of source copied 1:1
origin set, no size throws unless source matches remaining region source stretched over remaining region patch copy at origin
origin + size set throws unless source matches size source scaled to fill size at origin 1:1 copy at origin, cut to min(source, size)
mipLevel set same rules against that mip's size blit into that mip clip against that mip
writeAsync(blob) same rules after decode resized during decode, no blit decoded full, clipped on write
writeChannels same rules stretched channel blit clipped channel blit

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

pkg.pr.new

packages
Ready to be installed by your favorite package manager ⬇️

https://pkg.pr.new/software-mansion/TypeGPU/eslint-plugin-typegpu@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/tgpu-gen@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/tinyest-for-wgsl@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/typegpu@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/cli@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/color@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/gl@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/noise@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/radiance-cascades@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/react@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/sdf@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/three@4189b73eb474a4a04d9751ed1008c35698f812a3
https://pkg.pr.new/software-mansion/TypeGPU/unplugin-typegpu@4189b73eb474a4a04d9751ed1008c35698f812a3

benchmark
view benchmark

commit
view commit

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Bundle size comparison (import * as ... in PR vs import * as ... in target):

🟢 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 ($${\color{red}+15.7\%}$$)
STATIC_allImports.ts 300.78 kB ($${\color{red}+2.6\%}$$)
STATIC_tgpu.ts 272.49 kB ($${\color{red}+1.9\%}$$)
tgpu_fn.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_init.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_lazy.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_slot.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_const.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_unroll.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_resolve.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_accessor.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_comptime.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_vertexFn.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_computeFn.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_fragmentFn.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_privateVar.ts 272.50 kB ($${\color{red}+1.9\%}$$)
tgpu_vertexLayout.ts 272.51 kB ($${\color{red}+1.9\%}$$)
tgpu_workgroupVar.ts 272.51 kB ($${\color{red}+1.9\%}$$)
tgpu_initFromDevice.ts 272.51 kB ($${\color{red}+1.9\%}$$)
tgpu_bindGroupLayout.ts 272.51 kB ($${\color{red}+1.9\%}$$)
tgpu_mutableAccessor.ts 272.51 kB ($${\color{red}+1.9\%}$$)
tgpu_resolveWithContext.ts 272.51 kB ($${\color{red}+1.9\%}$$)
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 ($${\color{green}-3.1\%}$$)
tgpu_initFromDevice.ts 263.47 kB ($${\color{green}-3.3\%}$$)
tgpu_resolve.ts 164.59 kB ($${\color{green}-39.6\%}$$)
tgpu_resolveWithContext.ts 164.53 kB ($${\color{green}-39.6\%}$$)
tgpu_bindGroupLayout.ts 68.97 kB ($${\color{green}-74.7\%}$$)
tgpu_mutableAccessor.ts 65.97 kB ($${\color{green}-75.8\%}$$)
tgpu_accessor.ts 65.96 kB ($${\color{green}-75.8\%}$$)
tgpu_privateVar.ts 65.31 kB ($${\color{green}-76.0\%}$$)
tgpu_workgroupVar.ts 65.30 kB ($${\color{green}-76.0\%}$$)
tgpu_const.ts 64.55 kB ($${\color{green}-76.3\%}$$)
tgpu_fn.ts 38.14 kB ($${\color{green}-86.0\%}$$)
tgpu_fragmentFn.ts 38.14 kB ($${\color{green}-86.0\%}$$)
tgpu_vertexFn.ts 37.96 kB ($${\color{green}-86.1\%}$$)
tgpu_computeFn.ts 37.66 kB ($${\color{green}-86.2\%}$$)
tgpu_vertexLayout.ts 26.79 kB ($${\color{green}-90.2\%}$$)
tgpu_comptime.ts 14.48 kB ($${\color{green}-94.7\%}$$)
tgpu_unroll.ts 1.66 kB ($${\color{green}-99.4\%}$$)
tgpu_slot.ts 1.54 kB ($${\color{green}-99.4\%}$$)
tgpu_lazy.ts 1.19 kB ($${\color{green}-99.6\%}$$)

If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

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]
Loading
---
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]
Loading
---
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]
Loading

@reczkok
reczkok marked this pull request as ready for review July 23, 2026 16:15
Copilot AI review requested due to automatic review settings July 23, 2026 16:15
@pullfrog

pullfrog Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

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 →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 add texture.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.

Comment thread packages/typegpu/src/core/texture/textureWrite.ts
Comment thread packages/typegpu/src/core/texture/texture.ts
Comment thread packages/typegpu/src/common/writeChannels.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — incremental review of commit ca5e3e6 ("review fix"), which completes writeChannels validation to match the behavior of imageWriteForLayer.

  • writeChannels validates source-to-target size mismatches — the helper now computes a default target size via defaultWriteSize (respecting mipLevel and origin), throwing when the source and target dimensions differ and no fit mode is specified. This was previously silently resampled.
  • Error-path test for the missing-fit case — a new test confirms that writeChannels with a 16×16 source on a 64×64 texture throws the expected mismatch error when no fit option is provided.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@reczkok
reczkok force-pushed the impr/more-capable-texture-write branch from d7a9fc6 to 4189b73 Compare July 23, 2026 23:27

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 writescopyImageToTexture stages through a 2D texture + copyTextureToTexture since the WebGPU copy-image entry point only accepts 2D destinations. Resampled 3D writes render into individual depth slices via depthSlice in 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 improvementsloadOp and viewport options allow resampled writes to target sub-regions without clearing untouched texels (coversFullMipLevel gates the clear vs load decision). depthSlice support enables per-slice rendering in 3D textures.
  • Mock refactoring — test mock factories (createDeviceMock, createCommandEncoderMock, createRenderPassEncoderMock) extracted with exported types, and copyTextureToTexture / depthSlice recording added to gpuCallTimeline.
  • New tests — covers 3D staging, resampling into depth slices, and mip-scaled depth layer validation.
  • Docs — new texture-write-3d example with a volumetric ray-marching sandbox; browser-support caution for GPUCopyExternalImageSource types.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants