aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/gl
diff options
context:
space:
mode:
authorGravatar bsalomon@google.com <bsalomon@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-02-14 17:03:16 +0000
committerGravatar bsalomon@google.com <bsalomon@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-02-14 17:03:16 +0000
commit621dfe6c847a6372abc1d14b38c6c44198ef76b7 (patch)
tree949509a35c0ad91a78cb964e1e82595ab5b3b914 /src/gpu/gl
parented3ee6418ac1dc527db68c75c1d3d9a8d80ebeb0 (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.cpp4
-rw-r--r--src/gpu/gl/GrGLCaps.h6
-rw-r--r--src/gpu/gl/GrGLColorConversionTest.cpp242
-rw-r--r--src/gpu/gl/GrGLColorConversionTest.h36
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