Bug 137793 - Calling glReadPixels with BGRA format on an NVIDIA machine with an opaque context returns the wrong alpha values.
Summary: Calling glReadPixels with BGRA format on an NVIDIA machine with an opaque con...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebGL (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords:
: 137752 (view as bug list)
Depends on:
Blocks:
 
Reported: 2014-10-16 15:22 PDT by Roger Fong
Modified: 2014-10-19 00:19 PDT (History)
7 users (show)

See Also:


Attachments
patch (4.15 KB, patch)
2014-10-16 16:05 PDT, Roger Fong
bfulgham: review-
Details | Formatted Diff | Diff
patch (2.13 KB, patch)
2014-10-16 23:50 PDT, Roger Fong
dino: review+
Details | Formatted Diff | Diff
patch (2.67 KB, patch)
2014-10-17 17:08 PDT, Roger Fong
dino: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Roger Fong 2014-10-16 15:22:04 PDT
When we call readPixels from the WebGLRenderingContext note that we spit out an error if GL_BGRA format is used.
However, we for some reason use this format when reading the rendering the results for the purposes of either reading the rendering results into an ImageData or a canvas. 
This color format seems to fail specifically fail on NVIDIA cards and in particular the alpha channel can be read incorrectly when the alpha is off for the context. However, I will make a blanket change here for all devices and context attribute settings for alpha to be consistent with WebGLRenderingContext::readPixels which does the following:

    switch (format) {
    case GraphicsContext3D::ALPHA:
    case GraphicsContext3D::RGB:
    case GraphicsContext3D::RGBA:
        break;
    default:
        synthesizeGLError(GraphicsContext3D::INVALID_ENUM, "readPixels", "invalid format");
        return;
    }
Comment 1 Roger Fong 2014-10-16 16:05:19 PDT
Created attachment 239980 [details]
patch
Comment 2 Brent Fulgham 2014-10-16 16:12:10 PDT
Comment on attachment 239980 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=239980&action=review

I think this change will break whatever port uses the OpenGLES backend (EFL maybe?).

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGLCommon.cpp:254
> +    ::glReadPixels(0, 0, m_currentWidth, m_currentHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

I think this was here to support OpenGLES clients. See GraphicsContext3DOPenGLES.cpp, which looks like GraphicsContext3D::readPixelsAndConvertToBGRAIfNecessary, except that it swaps the R and B pixels.
Comment 3 Roger Fong 2014-10-16 23:44:41 PDT
*** Bug 137752 has been marked as a duplicate of this bug. ***
Comment 4 Roger Fong 2014-10-16 23:47:10 PDT
Chromium queries in RGBA, not BGRA and swaps if necessary, which is essentially what my patch will do, except only for NVIDIA cards where this is actually an issue.
Comment 5 Roger Fong 2014-10-16 23:50:27 PDT
Created attachment 240001 [details]
patch
Comment 6 Dean Jackson 2014-10-17 11:06:27 PDT
Comment on attachment 240001 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240001&action=review

> Source/WebCore/ChangeLog:13
> +        On an nvidia machine, when the context has alpha turned off, call glReadPixels with RGBA format and then convert to RGBA.

NVIDIA
Comment 7 Dean Jackson 2014-10-17 11:31:54 PDT
Comment on attachment 240001 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240001&action=review

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:73
> +        int totalBytes = width * height * 4;
> +        for (int i = 0; i < totalBytes; i += 4)
> +            std::swap(pixels[i], pixels[i + 2]);

I wonder if something like vImagePermuteChannels_ARGB8888 would do this faster.
Comment 8 Dean Jackson 2014-10-17 11:38:42 PDT
Comment on attachment 240001 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240001&action=review

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:72
> +        ::glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
> +        int totalBytes = width * height * 4;
> +        for (int i = 0; i < totalBytes; i += 4)

e.g.
vImage_Buffer src;
src.height = height;
src.width = width;
src.rowBytes = srcBytesPerRow;
src.data = srcRows;

vImage_Buffer dest;
dest.height = height;
dest.width = width;
dest.rowBytes = destBytesPerRow;
dest.data = destRows;

// Swap pixel channels from BGRA to RGBA.
const uint8_t map[4] = { 2, 1, 0, 3 };
vImagePermuteChannels_ARGB8888(&src, &dest, map, kvImageNoFlags);
Comment 9 Dean Jackson 2014-10-17 11:39:15 PDT
Comment on attachment 240001 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240001&action=review

>> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:72
>> +        for (int i = 0; i < totalBytes; i += 4)
> 
> e.g.
> vImage_Buffer src;
> src.height = height;
> src.width = width;
> src.rowBytes = srcBytesPerRow;
> src.data = srcRows;
> 
> vImage_Buffer dest;
> dest.height = height;
> dest.width = width;
> dest.rowBytes = destBytesPerRow;
> dest.data = destRows;
> 
> // Swap pixel channels from BGRA to RGBA.
> const uint8_t map[4] = { 2, 1, 0, 3 };
> vImagePermuteChannels_ARGB8888(&src, &dest, map, kvImageNoFlags);

Except you want the other way around :)
Comment 10 Roger Fong 2014-10-17 13:00:44 PDT
I'll try running instruments on a test page with
a) no swapping at all
b) the std::swap
c) the vImagePermuteChannels_ARGB8888
Comment 11 Roger Fong 2014-10-17 16:43:08 PDT
a) No reason to run this test really...all readPixelsAndConvertToBGRAIfNecessary does on mac is call glReadPixels, so the times are the same.

b)
Running Time	Self		Symbol Name
5012.0ms    3.8%	445.0	 readPixelsAndConvertToBGRAIfNecessary	                                         
4562.0ms    3.5%	1.0	 glReadPixels

c)
Running Time	Self	Symbol Name
5006.0ms    4.8%	1.0	readPixelsAndConvertToBGRAIfNecessary                               
4833.0ms    4.7%	0.0	glReadPixels

So it looks like the vaccelerator stuff is definitely faster, though I guess we already knew that to be the case.
Comment 12 Roger Fong 2014-10-17 17:08:02 PDT
Created attachment 240053 [details]
patch
Comment 13 Roger Fong 2014-10-17 17:13:42 PDT
(In reply to comment #11)
> a) No reason to run this test really...all
> readPixelsAndConvertToBGRAIfNecessary does on mac is call glReadPixels, so
> the times are the same.
> 
> b)
> Running Time	Self		Symbol Name
> 5012.0ms    3.8%	445.0	 readPixelsAndConvertToBGRAIfNecessary	              
> 
> 4562.0ms    3.5%	1.0	 glReadPixels
> 
> c)
> Running Time	Self	Symbol Name
> 5006.0ms    4.8%	1.0	readPixelsAndConvertToBGRAIfNecessary                  
> 
> 4833.0ms    4.7%	0.0	glReadPixels
> 
> So it looks like the vaccelerator stuff is definitely faster, though I guess
> we already knew that to be the case.

What we're comparing is the differences between the two function calls (which is the time it takes to swap the 1st and 3rd channels). 
Using accelerator stuff is about 30% better.
Comment 14 Dean Jackson 2014-10-17 17:14:34 PDT
Comment on attachment 240053 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240053&action=review

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:89
> +    // NVIDIA drivers have a bug where calling readPixels in BGRA can return the wrong values for the alpha channel when the alpha is off for the context.
> +    if (!m_attrs.alpha && getExtensions()->isNVIDIA()) {
> +        ::glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
> +        vImage_Buffer src;
> +        src.height = height;
> +        src.width = width;
> +        src.rowBytes = width*4;
> +        src.data = pixels;
> +
> +        vImage_Buffer dest;
> +        dest.height = height;
> +        dest.width = width;
> +        dest.rowBytes = width*4;
> +        dest.data = pixels;
> +
> +        // Swap pixel channels from BGRA to RGBA.
> +        const uint8_t map[4] = { 2, 1, 0, 3 };
> +        vImagePermuteChannels_ARGB8888(&src, &dest, map, kvImageNoFlags);

Won't you need the USE(ACCELERATE) around this too? Other ports won't work.
Comment 15 Tim Horton 2014-10-17 22:27:02 PDT
Comment on attachment 240053 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240053&action=review

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:72
> +    // NVIDIA drivers have a bug where calling readPixels in BGRA can return the wrong values for the alpha channel when the alpha is off for the context.

Is this true for all NVIDIA drivers, historically? Is this likely to change? Should we be detecting the wrongness somehow instead of just flipping things around like this?

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:78
> +        src.rowBytes = width*4;

Spaces around the star, please.

> Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:84
> +        dest.rowBytes = width*4;

Ditto.
Comment 16 Roger Fong 2014-10-18 23:40:02 PDT
(In reply to comment #15)
> Comment on attachment 240053 [details]
> patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=240053&action=review
> 
> > Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:72
> > +    // NVIDIA drivers have a bug where calling readPixels in BGRA can return the wrong values for the alpha channel when the alpha is off for the context.
> 
> Is this true for all NVIDIA drivers, historically? Is this likely to change?
> Should we be detecting the wrongness somehow instead of just flipping things
> around like this?

We'd then have to make two queries to glReadPixels then which seems unfortunate.
I've tested on 3 devices, dating back to the 2009 macbook pro card.

> 
> > Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:78
> > +        src.rowBytes = width*4;
> 
> Spaces around the star, please.
> 
> > Source/WebCore/platform/graphics/opengl/GraphicsContext3DOpenGL.cpp:84
> > +        dest.rowBytes = width*4;
> 
> Ditto.
Whoops missed those, I'll add those in.