Search code examples
windowsrustdirectxwindows-graphics-capture

Distorted Image Graphics Capture Api


I'm trying to create a simple screen-capturing library in Rust, but it has problems with maximized windows (monitor capture and not maximized windows capture work fine).

I copied most of my code from obs studio winrt-capture.cpp

But I Don't know why everything works except maximized apps

        // Create Frame Pool
        let frame_pool = Direct3D11CaptureFramePool::Create(
            &direct3d_device,
            DirectXPixelFormat::R8G8B8A8UIntNormalized,
            1,
            item.Size()?,
        )?;

        // Create Capture Session
        let session = frame_pool.CreateCaptureSession(&item)?;

    // On Frame Arrived
    frame_pool.FrameArrived(
            &TypedEventHandler::<Direct3D11CaptureFramePool, IInspectable>::new({
                move |frame, _| {
                    // Get Texture Stuff
                    let frame = frame.as_ref().unwrap().TryGetNextFrame()?;
                    let frame_content_size = frame.ContentSize()?;
                    let frame_surface = frame.Surface()?;
                    let frame_surface = frame_surface.cast::<IDirect3DDxgiInterfaceAccess>()?
                    let frame_surface = unsafe { frame_surface.GetInterface::<ID3D11Texture2D>()? };

                    let mut desc = D3D11_TEXTURE2D_DESC::default();
                    unsafe { frame_surface.GetDesc(&mut desc) }

                    if desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM {
                        if frame_content_size.Width != last_size.Width
                            || frame_content_size.Height != last_size.Height
                        {
                            let direct3d_device_recreate = &direct3d_device_recreate;
                            frame_pool_recreate
                                .Recreate(
                                    &direct3d_device_recreate.inner,
                                    DirectXPixelFormat::R8G8B8A8UIntNormalized,
                                    1,
                                    frame_content_size,
                                )
                                .unwrap();

                            last_size = frame_content_size;

                            return Ok(());
                        }

                        let texture_width = desc.Width;
                        let texture_height = desc.Height;

                        // Copy Texture Settings
                        let texture_desc = D3D11_TEXTURE2D_DESC {
                            Width: texture_width,
                            Height: texture_height,
                            MipLevels: 1,
                            ArraySize: 1,
                            Format: DXGI_FORMAT_R8G8B8A8_UNORM,
                            SampleDesc: DXGI_SAMPLE_DESC {
                                Count: 1,
                                Quality: 0,
                            },
                            Usage: D3D11_USAGE_STAGING,
                            BindFlags: 0,
                            CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
                            MiscFlags: 0,
                        };

                        // Create A Texture That CPU Can Read
                        let mut texture = None;
                        unsafe {
                            d3d_device_frame_pool.CreateTexture2D(
                                &texture_desc,
                                None,
                                Some(&mut texture),
                            )?
                        };
                        let texture = texture.unwrap();

                        // Copy The Real Texture To Copy Texture
                        unsafe { context.CopyResource(&texture, &frame_surface) };

                        // Map The Texture To Enable CPU Access
                        let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default();
                        unsafe {
                            context.Map(
                                &texture,
                                0,
                                D3D11_MAP_READ,
                                0,
                                Some(&mut mapped_resource),
                            )?
                        };

                        // Create A Slice From The Bits
                        let slice: &[Rgba] = unsafe {
                            std::slice::from_raw_parts(
                                mapped_resource.pData as *const Rgba,
                                (texture_desc.Height * mapped_resource.RowPitch) as usize
                                    / std::mem::size_of::<Rgba>(),
                            )
                        };

                        // Send The Frame To Callback Struct
                        trigger_frame_pool.lock().on_frame_arrived(
                            slice,
                            texture_width,
                            texture_height,
                        );

                        // Unmap Copy Texture
                        unsafe { context.Unmap(&texture, 0) };
                    }

                    Result::Ok(())
                }
            }),
        )?;

A weird thing I noticed is that the size of the frame pool is 1922x1033 and my monitor is 1920x1080 so its width is even higher than my monitor's width but I'm not sure if that's the problem because I checked and obs item size was 1922x1033 too I don't know if it's handled somewhere or not.

Edit: After investigating a bit more I think I found the problem I checked the pixels and found out that sometimes the alpha value is not 255 which means I'm reading the mapped resource wrong.

Based on d3d11_mapped_subresource docs :

For D3D_FEATURE_LEVEL_10_0 and higher, the pointer is aligned to 16 bytes. For lower than D3D_FEATURE_LEVEL_10_0, the pointer is aligned to 4 bytes.

which I think it is automatically handled when I am creating a slice because it works and Rgba struct alignment happens to be like this.

but there was a Note: The runtime might assign values to RowPitch and DepthPitch that are larger than anticipated because there might be padding between rows and depth.

I think the problem will be fixed if I handle this.

Edit 2: I was right because:
window size: 1922x1033
Anticipated Byte Size 7941704 (1922 * 1033 * 4)
Byte Size: 8065664 (Height * mapped_resource.RowPitch)

Now the question becomes where is the padding?

Edit 3: It's Fixed with this code:

                        let mut vec = Vec::new();
                        let slice = if texture_desc.Width * texture_desc.Height * 4
                            == texture_desc.Height * mapped_resource.RowPitch
                        {
                            // Means There Is No Padding And We Can Do Our Work
                            unsafe {
                                std::slice::from_raw_parts(
                                    mapped_resource.pData as *const Rgba,
                                    (texture_desc.Height * mapped_resource.RowPitch) as usize
                                        / std::mem::size_of::<Rgba>(),
                                )
                            }
                        } else {
                            for i in 0..texture_desc.Height {
                                println!("{i}");
                                let slice = unsafe {
                                    std::slice::from_raw_parts(
                                        mapped_resource
                                            .pData
                                            .add((i * mapped_resource.RowPitch) as usize)
                                            as *mut Rgba,
                                        texture_desc.Width as usize,
                                    )
                                };
                                vec.extend_from_slice(slice);
                            }

                            vec.as_slice()
                        };

I don't know if this is the most optimized way of doing it or not (please provide faster way if you know) but it works.


Solution

  • From Image Stride

    When a video image is stored in memory, the memory buffer might contain extra padding bytes after each row of pixels. The padding bytes affect how the image is stored in memory, but do not affect how the image is displayed.

    The stride is the number of bytes from one row of pixels in memory to the next row of pixels in memory. Stride is also called Pitch. If padding bytes are present, the stride is wider than the width of the image, as shown in the following illustration:

    enter image description here