← 输入框|github.com|刷新
Skip to content

Image uploading is insufficiently expressive to be optimized #4185

Open

Description

@kainino0x

Currently, if you have a PNG or JPG for example, and want to upload it into WebGPU, you must upload through an ImageBitmap (... or a canvas, technically). The basic set of ways to upload are:

  • HTMLImageElement -> createImageBitmap -> copyExternalImageToTexture
    (Note image.decode is not useful here, because createImageBitmap is already asynchronous, and already expresses the intent that the image must be decoded.)
  • fetch -> blob -> createImageBitmap -> copyExternalImageToTexture

The core problem is, it's impossible to express the full intent of "I have a URL for an image and I want to copy it into a GPUTexture". The createImageBitmap step in particular cannot know that the goal is to copy the image into a GPUTexture and then throw it away and never use it again, so there's no real way to avoid an extra copy in the middle.


EDIT: Note WebGPU no longer requires going through createImageBitmap() to upload images. But the inability to optimize and the inability to stream are still open issues.

Activity

  1. added this to the Milestone 2 milestone on Jun 14, 2023
  2. kainino0x commented on Jun 14, 2023

    @kainino0x
    ContributorAuthor

    IMO, uploading HTMLImageElement (#3430) is not a great solution to this, though it is a better stopgap than what we have now. My reasoning:

    • It's still not possible to know whether the intent is to put the HTMLImageElement in the DOM or upload it to WebGL/WebGPU. image.decode could theoretically express intent to put it in the DOM, but it's already often used for WebGL as well. And even so, not calling image.decode is a very poor signal for what you want to do with the image.
    • It does not work in workers.
  3. kainino0x commented on Jun 14, 2023

    @kainino0x
    ContributorAuthor

    I think my ideal path would look something like:

    • fetch -> blob -> copyExternalImageToTexture

    That is, it's good for the network part to be separate from decode-and-upload, but decode-and-upload should be one step. However, that lacks a place for image decode to occur asynchronously. To spitball:

    1. do that but put decoding "on" the queue, so it's part of the copyExternalImageToTexture operation and doesn't block JS. This could be either:
      • a. the decode is a synchronous operation inside the device or queue timeline
      • b. something like pipeline creation where the decode can happen "somewhere else" (like in a worker thread) but the device timeline is blocked as soon as you try to use the target texture for something
    2. add an async method to create a texture from a blob, then if you want to copy from it you can copyTextureToTexture
      • EDIT: b. or not async, and it blocks when you try to use it
    3. add a "mapping"-like state to textures where the texture can be locked for the duration of the decode-and-upload
  4. shaoboyan commented on Jun 15, 2023

    @shaoboyan
    Contributor

    That is, it's good for the network part to be separate from decode-and-upload, but decode-and-upload should be one step.

    IIUC, this means we want to config the (image) decoder output resource to GPUTexture. It's a bit like ImportExternalTexture which we possible to hold "undecoded" content and "decode" when user use it (in draw or dispatch).
    So I think I'm tend on option 1 (do that but put decoding "on" the queue), which the decode states is checked only when the resource is in use (and then block for decoding, or decode task is already finished).

    But I don't think copyExternalImageToTexture API is a good place for it. Instead, I prefer something like importExternalTexture.

  5. greggman commented on Jun 16, 2023

    @greggman
    Member

    I'm not sure where this discussion is leading

    fetch -> blob -> copyExternalImageToTexture

    Seems scary to me (maybe it shouldn't?). In my mind I have an image loading library (libpng, libjpg, libwebp) and ideally I'd just like to use them for whatever purpose I want (WebGL, WebGPU, Canvas2D, custom image processing, ML, etc...) Those libraries happen to be built into the browser and are made to run async so I just need well designed API to access them.

    ImageBitmap or something like it, seems like the direction there, just add some more flags? .... or not. I can see the conflict. It would be strange to pass in {usage: "webgpu"} to tell createImageBitmap that the ultimate destination for this image is "webgpu". On the other hand, it seems really strange for `copyExternalImageToTexture" to be the place that needs to handle load/decode every possible "external image" including hi-color images, etc for a blob

    In other words it would be strange to me if WebGPU has a image loader in it's API. The current model seems to be WebGPU mostly does raw textures and buffers. The loading/decoding all happens elsewhere, outside of WebGPU, only after it's loaded/decoded, is the data passed to WebGPU.

    That seems like normal model. I don't think OpenGL, Metal, DirectX, Vulkan have image loaders.

  6. kainino0x commented on Jun 16, 2023

    @kainino0x
    ContributorAuthor

    That seems like normal model. I don't think OpenGL, Metal, DirectX, Vulkan have image loaders.

    I think the distinction is that it's common for native applications to use their own decoders, whereas on the Web we strongly prefer apps to use the browser's builtin (async) decoders. Where a native application could easily decode directly into mapped memory, on the Web AFAIK we don't have a way to ask the built-in decoders to do that. Maybe it could be added to WebCodecs' ImageDecoder or something, but I think it would still have to be synchronous. Providing a higher-level API would let the browser implement this however it wants, and asynchronously.

    On the other hand, it seems really strange for `copyExternalImageToTexture" to be the place that needs to handle load/decode every possible "external image" including hi-color images, etc for a blob

    This doesn't strike me as a problem. The browser already does this for createImageBitmap, we're just removing the intermediate ImageBitmap object from the equation.

  7. greggman commented on Jun 17, 2023

    @greggman
    Member

    On the other hand, it seems really strange for `copyExternalImageToTexture" to be the place that needs to handle load/decode every possible "external image" including hi-color images, etc for a blob

    This doesn't strike me as a problem. The browser already does this for createImageBitmap, we're just removing the intermediate ImageBitmap object from the equation.

    The difference to me is ImageBitmap is a generic image container, I can pass an ImageBitmap to canvas2DCtx.drawImage, to webgl.texImage2D, to webgpu's copyExternalImageToTexture, where as blob->copyExternalImageToTexture is specifically WebGPU only. That means the problem being solved (getting data ready to use for a particular API) is only being solved for a single API

    All kinds of new features and issues will be appearing soon. For example, if you decode a jpeg with a gainmap for hi-color, do you get 8bits or 16bits or 10bits, 16 floats or 16bit uint? etc.. Ideally I want to be able to load .jpg with gainmap and draw it in canvas2d, load it in webgl, and load it webgpu.

    If I load a PNG with a display-p3 color profile what data should I get from it? I want to be able to draw it into a display-p3 canvas with canvas2d, webgl, and webgpu. Does that have any implications for how to design a solution?

  8. kainino0x commented on Jun 27, 2023

    @kainino0x
    ContributorAuthor

    That means the problem being solved (getting data ready to use for a particular API) is only being solved for a single API

    This seems OK to me. If we keep the goal that we want all the info up front before we start decoding, then trying to have one API for this (e.g. more ImageBitmap parameters) only makes things more complicated. The list of things we need to know keeps growing:

    • Need to know the ImageBitmap is for WebGPU
    • That it will only be used in copyExternalImageToTexture
    • The format of the destination texture
    • Also want to match the alpha/colorspace/flip settings to avoid extra conversion

    So I think it's both easiest to standardize, and fine for developers, to solve this separately for each API in a way where all the final usage is known implicitly, and the browser can just use whatever info it wants. The only benefit I see to extended the ImageBitmap API is maybe we could make all of the new parameters backward compatible such that new code still works in older browsers. But ultimately I think this will be more painful for developers.

    All kinds of new features and issues will be appearing soon. For example, if you decode a jpeg with a gainmap for hi-color, do you get 8bits or 16bits or 10bits, 16 floats or 16bit uint? etc.. Ideally I want to be able to load .jpg with gainmap and draw it in canvas2d, load it in webgl, and load it webgpu.

    If I load a PNG with a display-p3 color profile what data should I get from it? I want to be able to draw it into a display-p3 canvas with canvas2d, webgl, and webgpu. Does that have any implications for how to design a solution?

    Implementations (and specs) should share code (prose) between blob-to-GPUTexture, blob-to-ImageBitmap, ImageBitmap-to-whatever, etc. in such a way that they get these implicitly, and we don't have to respecify/reimplement all of the corner cases just because we add a new entry point. Of course, it means we have to test these cases, but that was true anyway.

    It seems the same as, if we add gainmapped jpegs, then we have to add gainmapped ImageBitmaps, now we have to handle that in copyExternalImageToTexture.

  9. kainino0x commented on Jun 28, 2023

    @kainino0x
    ContributorAuthor

    To distill a particular point that came up while talking to Gregg: there are AFAIK 4ish representations of encoded images in the web platform:

    • HTMLImageElement (not in workers)
    • Raw data
      • Blob (createImageBitmap can turn this into an ImageBitmap)
      • BufferSource (ImageDecoder can turn this into a VideoFrame)
      • Stream, e.g. (await fetch()).body (ImageDecoder can turn this into a VideoFrame)

    In all cases the object can point to invalid data, so the error cases are roughly similar for all 4, but in the case of both HTMLImageElement and streams, we either have to require the image to be fully downloaded (iirc WebGL does this for HTMLImageElement), or deal with the fact that the download may not be finished yet. There is some value to pipelining decoding with downloading (see ImageDecoder), and we should keep that in mind.

    Simply accepting one or more of these (fully downloaded) allows a browser to either do a blocking decode like you get in WebGL today, or implement what I called option 1 above. But if e.g. Chrome goes and makes the effort to implement option 1, we'll create pressure for other browsers to do it too, so I'd want to have sign-off on the idea before we do - or a decision that we should not do that, and prioritize something else instead, like a pipelining design.

    Offhand, a pipelining design could be for example option 2/3, but taking a stream (or HTMLImageElement) instead.

  10. kainino0x commented on Jun 29, 2023

    @kainino0x
    ContributorAuthor

    I've revived #3430 as the possible stopgap (with or without ImageData).

  11. added
    api-milestone-2-202502api issues that were in milestone 2 before we triaged milestone 1 on 2025-02-19
    on Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiWebGPU APIapi-milestone-2-202502api issues that were in milestone 2 before we triaged milestone 1 on 2025-02-19

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions