Screen Capture using CopyTextureToBuffer. WGPUBufferMapAsyncStatus set to Error.

39 views
Skip to first unread message

Jean-Colas Prunier

unread,
Feb 2, 2023, 11:05:19 AM2/2/23
to Dawn Graphics
(sorry deleted my first message as I realized there was a bug in the code, so I wanted to fix this before posting in case it fixed the issue).

I wanted to test ` CopyTextureToBuffer` functionality but have failed so far. I used the code below, which I copied for the most part from `CopyCommandValidationTests`.

When I look at the value of `WGPUBufferMapAsyncStatus` in SaveToFile the callback function called by  ` MapAsync` I constantly get an error message.

- I create a basic texture (empty).
- Create a buffer to copy the data to.
- use  CopyTextureToBuffer for the cmd.
- push the command to the queue.
- call  MapAsync on the buffer so that it calls SaveToFile once the command has been fully executed.

I would appreciate your input as to what I need to do better here (if that's obvious) or how I should be doing it. Thank you.

```
void CaptureScreen()
{  
    uint64_t offset = 0;
   
    uint32_t bytesPerPixel = GetTexelBlockSizeInBytes(kColorFormat);
    uint32_t bytesPerRow = Align(width * bytesPerPixel, kTextureBytesPerRowAlignment);
   
    uint32_t rowsPerImage = height;

    // these are declared as globals
    /* uint64_t */ bufferSize = BufferSizeForTextureCopy(width, height, 1);
    /* wgpu::Buffer */ destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);

    wgpu::ImageCopyBuffer imageCopyBuffer = CreateImageCopyBuffer(destination, offset, bytesPerRow, rowsPerImage);

    wgpu::Texture source  = Create2DTexture(device, wgpu::TextureDimension::e2D,
        kColorFormat,  width, height, 1, 1, 1);

    wgpu::ImageCopyTexture imageCopyTexture = CreateImageCopyTexture(source, 1, {0, 0, 0});
    wgpu::Extent3D extent3D = {width, height, 1}; 

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &extent3D);
    wgpu::CommandBuffer commands = encoder.Finish();
    device.GetQueue().Submit(1, &commands);

    destination.MapAsync(wgpu::MapMode::Read, 0, bufferSize, StoreImage, nullptr);
}
```
Message has been deleted

Jean-Colas Prunier

unread,
Feb 2, 2023, 1:45:39 PM2/2/23
to Dawn Graphics
Ok i made some progress. i figured there was a synchronization issue since I was missing something that would essentially not call the StoreImage() until the command was executed. So I played with `OnSubmittedWorkDone` but I `status` is still equal to WGPUBufferMapAsyncStatus_Error  ;-(

Would be great to get your advice!) Thank you.

```
void StoreImage(WGPUBufferMapAsyncStatus status, void* userData)
{
    // keep getting WGPUBufferMapAsyncStatus_Error here ;-*(*
    if (status == WGPUBufferMapAsyncStatus_Success) {
       
    }
}

// were bufferSize and buffer are global
uint64_t bufferSize;
wgpu::Buffer buffer;
void CallbackWorkDone(WGPUQueueWorkDoneStatus status, void * userdata)
{
    buffer.MapAsync(wgpu::MapMode::Read, 0, bufferSize, StoreImage, nullptr);
}

void CaptureScreen()
{  
    ...
    bufferSize = BufferSizeForTextureCopy(width, height, 1);
     buffer  = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
    ...

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &extent3D);
    wgpu::CommandBuffer commands = encoder.Finish();
    queue.Submit(1, &commands);

    queue.OnSubmittedWorkDone(0, CallbackWorkDone, nullptr);
}

Mark Sibly

unread,
Feb 2, 2023, 1:56:04 PM2/2/23
to Jean-Colas Prunier, Dawn Graphics
>  /* wgpu::Buffer */ destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);

The 'destination' buffer possibly needs to have BufferUsage::MapRead flag set too.

I also recommend using device.SetUncapturedErrorCallback(&deviceError, nullptr) if you're not already to 'catch' dawn errors, it's very good at finding issues like this.

Bye!
Mark

Jean-Colas Prunier

unread,
Feb 3, 2023, 4:42:27 AM2/3/23
to Dawn Graphics
This fixed the problem. Thank you so much. I had an example of that right in front of my eyes, but I overlooked that part. Appreciate your help. Thank you.

And thanks for the  SetUncapturedErrorCallback tip. I will start using it.

Side question). I didn't find a way of acquiring the texture from the swapchain. Only the TextureView. I looked at GetCurrentTextureView in the code but that seems a bit too far off for me right now to try to hack the function to get access to the texture. For the time being I render to a texture and use this texture for the screen capture test. But I was wondering if there was a way to get the Texture from a TextureView? That way I could simply use the swapchain rather than a bespoke texture.

Austin Eng

unread,
Feb 3, 2023, 1:02:10 PM2/3/23
to Jean-Colas Prunier, Dawn Graphics
On Fri, Feb 3, 2023 at 1:42 AM Jean-Colas Prunier <j...@jean-colas.com> wrote:
This fixed the problem. Thank you so much. I had an example of that right in front of my eyes, but I overlooked that part. Appreciate your help. Thank you.

And thanks for the  SetUncapturedErrorCallback tip. I will start using it.

Side question). I didn't find a way of acquiring the texture from the swapchain. Only the TextureView. I looked at GetCurrentTextureView in the code but that seems a bit too far off for me right now to try to hack the function to get access to the texture. For the time being I render to a texture and use this texture for the screen capture test. But I was wondering if there was a way to get the Texture from a TextureView? That way I could simply use the swapchain rather than a bespoke texture.

This is https://github.com/webgpu-native/webgpu-headers/issues/89. The native API has a slight difference from the JS API which returns a texture. I think the consensus is to switch the native API to use WGPUTexture to match, but we haven't gotten around to it yet.
 



On Thursday, February 2, 2023 at 7:56:04 PM UTC+1 mark...@gmail.com wrote:
>  /* wgpu::Buffer */ destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);

The 'destination' buffer possibly needs to have BufferUsage::MapRead flag set too.

I also recommend using device.SetUncapturedErrorCallback(&deviceError, nullptr) if you're not already to 'catch' dawn errors, it's very good at finding issues like this.

Bye!
Mark

--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dawn-graphics/39417e3c-d621-41e8-bc1f-9a65634f89f3n%40googlegroups.com.

Jean-Colas Prunier

unread,
Feb 3, 2023, 1:24:15 PM2/3/23
to Dawn Graphics
Thanks again Mark for your help. SetUncapturedErrorCallback has been super useful and it helped me progress. I tested that copying textures to buffers worked but I only got it to work with textures that have sampleCount = 1. If I try with a texture that has a SampleCount > 1 then I get the following error:

```
Validation error: [Texture] sample count (4) is not 1 when copying to or from a buffer.
 - While encoding [CommandEncoder].CopyTextureToBuffer([Texture], [Buffer], [Extent3D width:640, height:480, depthOrArrayLayers:1]).
Validation error: [Invalid CommandBuffer] is invalid.
    at ValidateObject (../../src/dawn/native/Device.cpp:643)
    at ValidateSubmit (../../src/dawn/native/Queue.cpp:428)

```
And that makes sense). All examples I saw for copying textures to buffers had a sample count set to 1. What would be recommended for making this work with textures whose sample count > 1? Basically I have tested msaa and that worked well and so I am using that texture as my resolveTaget for the swapchain. Ideally I'd like to save the anti-aliased render.

Thanks again.
On Thursday, February 2, 2023 at 7:56:04 PM UTC+1 mark...@gmail.com wrote:

Kai Ninomiya

unread,
Feb 3, 2023, 2:58:42 PM2/3/23
to Jean-Colas Prunier, Dawn Graphics
If you just want the *anti-aliased* render, you don't need all of the individual sample data. In that case you should save the resolve target instead of the multisampled attachment. The multisampled data can't be treated as an actual image/screenshot anyway because it has too much data per pixel - it has to be resolved to display it.

If you do need to read directly out of a multi-sampled texture, the only way is to bind it to a shader as texture_multisampled_2d. Check out this test helper code which "emulates" a single-texel multi-sample texture-to-buffer copy:

-Kai (he/they)


--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.

Jean-Colas Prunier

unread,
Feb 3, 2023, 4:34:22 PM2/3/23
to Dawn Graphics
Thanks Kai,

Yes, that's what I figured, and I was left with the option to render the texture to a quad indeed. It would be great if we could get a "Resolve" kind of function as part of the CommandEncoder class?

But sorry to ask a trivial question (this was part of my initial question / comment from a previous message from this tread, which was basically "how do I get the texture as opposed to the TextureView from the swapchain)): how do you access the resolveTarget as a texture? As I said before, I was hoping to have a way of getting a texture from the swapchain as opposed to the texture view but from I get from Austin, this is not yet possible? Is there a way then?

Thank you. Hope it's ok with me asking all these questions. Let me know if this distracts you from your work.

Kai Ninomiya

unread,
Feb 3, 2023, 5:25:26 PM2/3/23
to Jean-Colas Prunier, Dawn Graphics
Oh, yeah. There's agreement to allow getting non-view textures from the swapchain, we just never got around to it. If you're up for it you can contribute it to Dawn:
https://dawn.googlesource.com/dawn.git/+/refs/heads/main/CONTRIBUTING.md

Otherwise as a workaround you can use an empty render pass to re-resolve into a texture you can read back from.

-Kai (he/they)


Jean-Colas Prunier

unread,
Feb 3, 2023, 5:41:34 PM2/3/23
to Dawn Graphics

I'd love to contribute, but I am not sure I'd have the right level for your team / project. I will have a look though. Happy to test anything for you guys though and keep making suggestions if this helps in the meantime. 

I will go with the quad solution for now then.

Kai Ninomiya

unread,
Feb 3, 2023, 6:01:16 PM2/3/23
to Jean-Colas Prunier, Dawn Graphics
I bet you can! It'll be mostly copying surrounding code with small tweaks - the hard part is only finding the correct code to duplicate, which we can help out with. You can always try to make it work locally and open a CL if you get it working. We're happy to give code pointers on the Dawn channel on Matrix: https://matrix.to/#/#webgpu-dawn:matrix.org

-Kai (he/they)


Jean-Colas Prunier

unread,
Feb 5, 2023, 10:03:18 AM2/5/23
to Dawn Graphics
Thanks Kai. I don't make any promise but 1) I will give it a thought 2) I will go through the contribution page and make the necessary steps to at least get setup up. I will also spend a bit of time looking at the TextureView method to see if I can more or less understand the structure without much help for now. For sure would need your help to make the right changes (starting to know what function specifically you'd like me to look into first). Anyway, I will get in touch (once I get my quad sorted out!!!))).

Jean-Colas Prunier

unread,
Feb 7, 2023, 10:02:19 AM2/7/23
to Dawn Graphics
"Otherwise as a workaround you can use an empty render pass to re-resolve into a texture you can read back from."

This worked fine (naturally).

However just to clarify a point: is it somehow / technically possible to use a multi-sampled texture to texture an object. I have been trying to this approach (in parallel to simple setting the resolveTarget to a 1 sample texture, which as I mentioned worked fine), but have failed managing to make this work (though I will persist). I have created a second pass, in which I used the multi-sampled texture (format BGRA8Unorm) from pass 1 as an input texture (mapped to a quad). I set:
```
 .texture = (wgpu::TextureBindingLayout) {
    .sampleType = wgpu::TextureSampleType::Float,
    .viewDimension = wgpu::TextureViewDimension::e2D,
    .multisampled = true,
}
```
Though not sure what ` sampleType` should be set to. I get a warning. I will keep playing around, but while I get simple texturing working (all texture with sampeCount = 1), I struggle getting the multiSample case working. Is it supposed to work at all? Am I missing something?

If you have any ideas, would love to hear them.

On Friday, February 3, 2023 at 11:25:26 PM UTC+1 kai...@google.com wrote:
Reply all
Reply to author
Forward
0 new messages