diff options
author | 2012-02-14 17:03:16 +0000 | |
---|---|---|
committer | 2012-02-14 17:03:16 +0000 | |
commit | 621dfe6c847a6372abc1d14b38c6c44198ef76b7 (patch) | |
tree | 949509a35c0ad91a78cb964e1e82595ab5b3b914 /src/gpu/gl | |
parent | ed3ee6418ac1dc527db68c75c1d3d9a8d80ebeb0 (diff) |
Add test to detect bias in conversion of frag shader floats to bytes
Review URL: http://codereview.appspot.com/5669045/
git-svn-id: http://skia.googlecode.com/svn/trunk@3191 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/gpu/gl')
-rw-r--r-- | src/gpu/gl/GrGLCaps.cpp | 4 | ||||
-rw-r--r-- | src/gpu/gl/GrGLCaps.h | 6 | ||||
-rw-r--r-- | src/gpu/gl/GrGLColorConversionTest.cpp | 242 | ||||
-rw-r--r-- | src/gpu/gl/GrGLColorConversionTest.h | 36 |
4 files changed, 288 insertions, 0 deletions
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp index d690ff3fe1..ee6c521a0f 100644 --- a/src/gpu/gl/GrGLCaps.cpp +++ b/src/gpu/gl/GrGLCaps.cpp @@ -19,6 +19,7 @@ void GrGLCaps::reset() { fStencilFormats.reset(); fStencilVerifiedColorConfigs.reset(); fMSFBOType = kNone_MSFBOType; + fByteColorConversion = kUnknown_GrGLByteColorConversion; fMaxFragmentUniformVectors = 0; fRGBA8RenderbufferSupport = false; fBGRAFormatSupport = false; @@ -42,6 +43,7 @@ GrGLCaps& GrGLCaps::operator = (const GrGLCaps& caps) { fStencilVerifiedColorConfigs = caps.fStencilVerifiedColorConfigs; fMaxFragmentUniformVectors = caps.fMaxFragmentUniformVectors; fMSFBOType = caps.fMSFBOType; + fByteColorConversion = caps.fByteColorConversion; fRGBA8RenderbufferSupport = caps.fRGBA8RenderbufferSupport; fBGRAFormatSupport = caps.fBGRAFormatSupport; fBGRAIsInternalFormat = caps.fBGRAIsInternalFormat; @@ -129,6 +131,8 @@ void GrGLCaps::init(const GrGLContextInfo& ctxInfo) { ctxInfo.hasExtension("GL_ARB_texture_storage") || ctxInfo.hasExtension("GL_EXT_texture_storage"); + fByteColorConversion = GrGLFloatToByteColorConversion(ctxInfo); + this->initFSAASupport(ctxInfo); this->initStencilFormats(ctxInfo); } diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h index a5318ebde8..27e42582c0 100644 --- a/src/gpu/gl/GrGLCaps.h +++ b/src/gpu/gl/GrGLCaps.h @@ -10,6 +10,7 @@ #ifndef GrGLCaps_DEFINED #define GrGLCaps_DEFINED +#include "GrGLColorConversionTest.h" #include "GrGLStencilBuffer.h" class GrGLContextInfo; @@ -102,6 +103,10 @@ public: GrPixelConfig config, const GrGLStencilBuffer::Format& format) const; + GrGLByteColorConversion byteColorConversion() const { + return fByteColorConversion; + } + /** * Reports the type of MSAA FBO support. */ @@ -211,6 +216,7 @@ private: int fMaxFragmentUniformVectors; MSFBOType fMSFBOType; + GrGLByteColorConversion fByteColorConversion; bool fRGBA8RenderbufferSupport : 1; bool fBGRAFormatSupport : 1; diff --git a/src/gpu/gl/GrGLColorConversionTest.cpp b/src/gpu/gl/GrGLColorConversionTest.cpp new file mode 100644 index 0000000000..f3975e6cd9 --- /dev/null +++ b/src/gpu/gl/GrGLColorConversionTest.cpp @@ -0,0 +1,242 @@ + +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrGLColorConversionTest.h" +#include "GrGLContextInfo.h" +#include "GrGLShaderVar.h" +#include "GrGLSL.h" + +/** + * We expect the floating point range [i - .5] / 255 through [i + .5] / 255 to + * round to be converted to byte value i when output from a fragment shader into + * byte-per-channel color format. To test the rounding rule we draws a 256 pixel + * wide strip. At pixel i FS writes values close to the high and low endpoints + * of this range are written as color components. After read pixels we check how + * the values were actually rounded. As a sanity check, a value smack dab in the + * middle of the range we expect to convert to i is also output. + */ +GrGLByteColorConversion + GrGLFloatToByteColorConversion(const GrGLContextInfo& ctxInfo) { + + bool fail = true; + + GrGLuint dstRb = 0; + GrGLuint dstFbo = 0; + GrGLuint vertBuf = 0; + GrGLuint vsId = 0; + GrGLuint fsId = 0; + GrGLuint progId = 0; + GrGLShaderVar posIn, tOut, tIn, colorOut; + GrStringBuilder vs, fs; + + const GrGLInterface* gl = ctxInfo.interface(); + GrGLSLGeneration gen = ctxInfo.glslGeneration(); + GrGLBinding binding = ctxInfo.binding(); + + // setup the dst + GR_GL_CALL(gl, GenFramebuffers(1, &dstFbo)); + GrAssert(0 != dstFbo); + GR_GL_CALL(gl, BindFramebuffer(GR_GL_FRAMEBUFFER, dstFbo)); + GR_GL_CALL(gl, GenRenderbuffers(1, &dstRb)); + GrAssert(0 != dstRb); + GR_GL_CALL(gl, BindRenderbuffer(GR_GL_RENDERBUFFER, dstRb)); + GR_GL_CALL(gl, RenderbufferStorage(GR_GL_RENDERBUFFER, + GR_GL_RGBA8, + 256, 1)); + GR_GL_CALL(gl, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_COLOR_ATTACHMENT0, + GR_GL_RENDERBUFFER, + dstRb)); + GR_GL_CALL(gl, Viewport(0, 0, 256, 1)); + GrGLenum status; + GR_GL_CALL_RET(gl, status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + if (status != GR_GL_FRAMEBUFFER_COMPLETE) { + goto FINISHED; + } + + GR_GL_CALL_RET(gl, vsId, CreateShader(GR_GL_VERTEX_SHADER)); + GR_GL_CALL_RET(gl, fsId, CreateShader(GR_GL_FRAGMENT_SHADER)); + GR_GL_CALL_RET(gl, progId, CreateProgram()); + + GR_GL_CALL(gl, Disable(GR_GL_CULL_FACE)); + GR_GL_CALL(gl, Disable(GR_GL_BLEND)); + GR_GL_CALL(gl, Disable(GR_GL_DEPTH_TEST)); + GR_GL_CALL(gl, Disable(GR_GL_DITHER)); + GR_GL_CALL(gl, Disable(GR_GL_SCISSOR_TEST)); + GR_GL_CALL(gl, Disable(GR_GL_STENCIL_TEST)); + GR_GL_CALL(gl, ColorMask(GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE)); + + // setup the geometry: a single tri that fills the screen + static const GrGLfloat verts[] = {-2.f, -1.f, + 1.f, -1.f, + 1.f, 2.f}; + GR_GL_CALL(gl, GenBuffers(1, &vertBuf)); + GR_GL_CALL(gl, BindBuffer(GR_GL_ARRAY_BUFFER, vertBuf)); + GR_GL_CALL(gl, BufferData(GR_GL_ARRAY_BUFFER, + sizeof(verts), + verts, + GR_GL_STATIC_DRAW)); + GR_GL_CALL(gl, EnableVertexAttribArray(0)); + GR_GL_CALL(gl, VertexAttribPointer(0, // index + 2, // vector size + GR_GL_FLOAT, + false, + 2 * sizeof(GrGLfloat), + 0)); // offset + + + const char* verStr = GrGetGLSLVersionDecl(binding, gen); + + posIn.set(GrGLShaderVar::kVec2f_Type, + GrGLShaderVar::kAttribute_TypeModifier, + "pos"); + tOut.set(GrGLShaderVar::kVec2f_Type, + GrGLShaderVar::kOut_TypeModifier, + "t"); + tIn.set(GrGLShaderVar::kVec2f_Type, + GrGLShaderVar::kIn_TypeModifier, + "t"); + bool declColor = GrGLSLSetupFSColorOuput(gen, "color", &colorOut); + + vs.append(verStr); + posIn.appendDecl(ctxInfo, &vs); + tOut.appendDecl(ctxInfo, &vs); + vs.appendf("void main() {\n" + " gl_Position = vec4(%s.xy, 0.0, 1.0);\n" + " %s = (%s + vec2(1.0, 1.0)) / 2.0;\n" + "}\n", + posIn.getName().c_str(), + tOut.getName().c_str(), + posIn.getName().c_str()); + + fs.append(verStr); + fs.append(GrGetGLSLShaderPrecisionDecl(binding)); + tIn.appendDecl(ctxInfo, &fs); + + if (declColor) { + colorOut.appendDecl(ctxInfo, &fs); + } + fs.append( "void main() {\n"); + fs.appendf(" float intVal = floor(%s.x * 256.0) ;\n", tIn.getName().c_str()); + fs.append( " float justAboveExpectedLowCutoff = (intVal - 0.49) / 255.0;\n"); + fs.append( " float justBelowExpectedHighCutoff = (intVal + 0.49) / 255.0;\n"); + fs.append( " float middleOfExpectedRange = intVal / 255.0;\n"); + fs.appendf(" %s = vec4(justAboveExpectedLowCutoff,justBelowExpectedHighCutoff,middleOfExpectedRange,0.0);\n", colorOut.getName().c_str()); + fs.append( "}\n"); + + GrGLint vsCompiled = GR_GL_INIT_ZERO; + const GrGLchar* vsStr = vs.c_str(); + const GrGLint vsLen = vs.size(); + GrAssert(0 != vsId); + GR_GL_CALL(gl, ShaderSource(vsId, 1, &vsStr, &vsLen)); + GR_GL_CALL(gl, CompileShader(vsId)); + GR_GL_CALL(gl, GetShaderiv(vsId, GR_GL_COMPILE_STATUS, &vsCompiled)); + if (!vsCompiled) { + goto FINISHED; + } + + GrGLint fsCompiled = GR_GL_INIT_ZERO; + const GrGLchar* fsStr = fs.c_str(); + const GrGLint fsLen = fs.size(); + GrAssert(0 != fsId); + GR_GL_CALL(gl, ShaderSource(fsId, 1, &fsStr, &fsLen)); + GR_GL_CALL(gl, CompileShader(fsId)); + GR_GL_CALL(gl, GetShaderiv(fsId, GR_GL_COMPILE_STATUS, &fsCompiled)); + if (!fsCompiled) { + goto FINISHED; + } + + GrGLint progLinked = GR_GL_INIT_ZERO; + GrAssert(0 != progId); + GR_GL_CALL(gl, AttachShader(progId, vsId)); + GR_GL_CALL(gl, AttachShader(progId, fsId)); + if (declColor) { + GR_GL_CALL(gl, BindFragDataLocation(progId, 0, + colorOut.getName().c_str())); + } + GR_GL_CALL(gl, BindAttribLocation(progId, 0, posIn.getName().c_str())); + GR_GL_CALL(gl, LinkProgram(progId)); + GR_GL_CALL(gl, GetProgramiv(progId, GR_GL_LINK_STATUS, &progLinked)); + if (!progLinked) { + goto FINISHED; + } + GR_GL_CALL(gl, UseProgram(progId)); + + GR_GL_CALL(gl, DrawArrays(GR_GL_TRIANGLES, 0, 3)); + uint32_t readData[256]; + // set to junk values just in case readPixels silently fails (as it is + // known to do on some Intel GMA drivers). + memset(readData, 0xab, sizeof(readData)); + GR_GL_CALL(gl, PixelStorei(GR_GL_PACK_ALIGNMENT, 4)); + GR_GL_CALL(gl, ReadPixels(0, 0, 256, 1, GR_GL_RGBA, + GR_GL_UNSIGNED_BYTE, readData)); + + bool foundHighBias = false; + bool foundLowBias = false; + for (int i = 0; i < 256 && (!foundHighBias || !foundLowBias); ++i) { + int nearLowCutoffVal = readData[i] & 0x000000ff; + int nearHighCutoffVal = (readData[i] & 0x0000ff00) >> 8; + int middle = (readData[i] & 0x00ff0000) >> 16; + + if (middle != i) { + goto FINISHED; + } + + int diff0 = i - nearLowCutoffVal; + int diff1 = nearHighCutoffVal - i; + if (diff0 < 0 || diff0 > 1) { + goto FINISHED; + } + if (diff1 < 0 || diff1 > 1) { + goto FINISHED; + } + + if (1 == diff0) { + foundHighBias = true; + } + + if (1 == diff1) { + foundLowBias = true; + } + } + fail = (foundHighBias && foundLowBias); + +FINISHED: + if (dstFbo) { + GR_GL_CALL(gl, DeleteFramebuffers(1, &dstFbo)); + } + if (dstRb) { + GR_GL_CALL(gl, DeleteRenderbuffers(1, &dstRb)); + } + if (vertBuf) { + GR_GL_CALL(gl, DeleteBuffers(1, &vertBuf)); + } + if (vsId) { + GR_GL_CALL(gl, DeleteShader(vsId)); + } + if (fsId) { + GR_GL_CALL(gl, DeleteShader(fsId)); + } + if (progId) { + GR_GL_CALL(gl, DeleteProgram(progId)); + } + + if (fail) { + GrPrintf("Could not categorize how float-to-byte conversion is " + "performed from frag shader output.\n"); + return kUnknown_GrGLByteColorConversion; + } + + if (foundHighBias) { + return kHigh_GrGLByteColorConversion; + } else if (foundLowBias) { + return kLow_GrGLByteColorConversion; + } else { + return kRound_GrGLByteColorConversion; + } +} diff --git a/src/gpu/gl/GrGLColorConversionTest.h b/src/gpu/gl/GrGLColorConversionTest.h new file mode 100644 index 0000000000..0eb1fb05dd --- /dev/null +++ b/src/gpu/gl/GrGLColorConversionTest.h @@ -0,0 +1,36 @@ + +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrTypes.h" + +#ifndef GrGLByteColorConversion_DEFINED +#define GrGLByteColorConversion_DEFINED + +class GrGLContextInfo; + +enum GrGLByteColorConversion { + kUnknown_GrGLByteColorConversion, + kHigh_GrGLByteColorConversion, + kRound_GrGLByteColorConversion, + kLow_GrGLByteColorConversion +}; + +/** + * When a fragment shader outputs to a RGBA8888 pixel format the floating point + * result has to be converted to bytes. We would expect that the conversion of + * floating-point component f to be something like round(f * 255). However, + * experience shows that is common for the conversion to be biased towards + * larger than expected byte values, particularly in the low part of the range. + * + * This function determines experimentally whether that is true for the current + * OpenGL context accessible via ctxInfo. + */ +GrGLByteColorConversion + GrGLFloatToByteColorConversion(const GrGLContextInfo& ctxInfo); + +#endif |