On 20 June 2012 23:35, Kenneth Graunke <span dir="ltr"><<a href="mailto:kenneth@whitecape.org" target="_blank">kenneth@whitecape.org</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

<div>On 06/20/2012 04:08 PM, Paul Berry wrote:<br>
> On i965, dFdx() and dFdy() are computed by taking advantage of the<br>
> fact that each consecutive set of 4 pixels dispatched to the fragment<br>
> shader always constitutes a contiguous 2x2 block of pixels in a fixed<br>
> arrangement known as a "sub-span".  So we calculate dFdx() by taking<br>
> the difference between the values computed for the left and right<br>
> halves of the sub-span, and we calculate dFdy() by taking the<br>
> difference between the values computed for the top and bottom halves<br>
> of the sub-span.<br>
><br>
> However, there's a subtlety when FBOs are in use: since FBOs use a<br>
> coordinate system where the origin is at the upper left, and window<br>
> system framebuffers use a coordinate system where the origin is at the<br>
> lower left, the computation of dFdy() needs to be negated for FBOs.<br>
<br>
</div>Nice catch.<br></blockquote><div><br></div><div>Thanks.  I was fortunate to find it--it cropped up while writing piglit tests for MSAA centroid behaviour, since those tests run in FBOs and involve checking that derivatives work properly.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div><br>
> This patch modifies the fragment shader back-ends to negate the value<br>
> of dFdy() when an FBO is in use.  It also modifies the code that<br>
> populates the program key (brw_wm_populate_key() and<br>
> brw_fs_precompile()) so that they always record in the program key<br>
> whether we are rendering to an FBO or to a window system framebuffer;<br>
> this ensures that the fragment shader will get recompiled when<br>
> switching between FBO and non-FBO use.<br>
><br>
> This will result in unnecessary recompiles of fragment shaders that<br>
> don't use dFdy().  To fix that, we will need to adapt the GLSL and<br>
> NV_fragment_program front-ends to record whether or not a given shader<br>
> uses dFdy().  I plan to implement this in a future patch series; I've<br>
> left FIXME comments in the code as a reminder.<br>
<br>
</div>Yeah, it looks like that kind of infrastructure is becoming increasingly<br>
important.<br>
<div><br>
> Fixes Piglit test "fbo-deriv".<br>
> ---<br>
>  src/mesa/drivers/dri/i965/brw_fs.cpp      |   10 ++++++++++<br>
>  src/mesa/drivers/dri/i965/brw_fs.h        |    3 ++-<br>
>  src/mesa/drivers/dri/i965/brw_fs_emit.cpp |   14 +++++++++++---<br>
>  src/mesa/drivers/dri/i965/brw_wm.c        |   10 ++++++++++<br>
>  src/mesa/drivers/dri/i965/brw_wm.h        |    3 ++-<br>
>  src/mesa/drivers/dri/i965/brw_wm_emit.c   |   15 +++++++++++----<br>
>  6 files changed, 46 insertions(+), 9 deletions(-)<br>
><br>
> diff --git a/src/mesa/drivers/dri/i965/brw_fs.cpp b/src/mesa/drivers/dri/i965/brw_fs.cpp<br>
> index 313e720..43efd68 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_fs.cpp<br>
> +++ b/src/mesa/drivers/dri/i965/brw_fs.cpp<br>
> @@ -1848,6 +1848,13 @@ brw_fs_precompile(struct gl_context *ctx, struct gl_shader_program *prog)<br>
>     struct brw_context *brw = brw_context(ctx);<br>
>     struct brw_wm_prog_key key;<br>
><br>
> +   /* As a temporary measure we assume that all programs use dFdy() (and hence<br>
> +    * need to be compiled differently depending on whether we're rendering to<br>
> +    * an FBO).  FIXME: set this bool correctly based on the contents of the<br>
> +    * program.<br>
> +    */<br>
> +   bool program_uses_dfdy = true;<br>
> +<br>
<br>
</div>I would just leave this as false.  Almost all fragment shaders that I've<br>
seen don't use dPdy, so arguably false is the common case (which is what<br>
precompile is arguably aiming for).  Though, it doesn't matter<br>
particularly much---the state settings selected here are fairly<br>
arbitrary, and at the end of the day, the actual compilation process<br>
will do the right thing regardless.<br>
<br>
Also, note that the precompile isn't used by default (unless you set the<br>
driconf option shader_precompile = true), so you most likely won't hit<br>
this at all.<br>
<br>
If you'd rather leave it, that's fine.  It's not a big deal either way.<br></blockquote><div><br></div><div>Hmm, thanks for the explanation.  I didn't previously understand the purpose of precompiling.  I have to admit I'm not thrilled about the code duplication between brw_fs_precompile() and brw_wm_populate_key().  In my experience, code duplication between an often-used and a rarely-used function tends to lead to bugs, because when people modify the often-used function they forget to modify the rarely-used function.  (In fact it looks like your recent patch "i965: Don't set brw_wm_prog_key::iz_lookup on Gen6+." just modified brw_wm_populate_key()).  I don't really have a good suggestion about how to eliminate the duplication, though.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div><div><br>
>     if (!prog->_LinkedShaders[MESA_SHADER_FRAGMENT])<br>
>        return true;<br>
><br>
> @@ -1892,6 +1899,9 @@ brw_fs_precompile(struct gl_context *ctx, struct gl_shader_program *prog)<br>
><br>
>     if (fp->Base.InputsRead & FRAG_BIT_WPOS) {<br>
>        key.drawable_height = ctx->DrawBuffer->Height;<br>
> +   }<br>
> +<br>
> +   if ((fp->Base.InputsRead & FRAG_BIT_WPOS) || program_uses_dfdy) {<br>
>        key.render_to_fbo = _mesa_is_user_fbo(ctx->DrawBuffer);<br>
>     }<br>
><br>
> diff --git a/src/mesa/drivers/dri/i965/brw_fs.h b/src/mesa/drivers/dri/i965/brw_fs.h<br>
> index 0c6c3e4..2c2cb6c 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_fs.h<br>
> +++ b/src/mesa/drivers/dri/i965/brw_fs.h<br>
> @@ -534,7 +534,8 @@ public:<br>
>                          struct brw_reg src);<br>
>     void generate_discard(fs_inst *inst);<br>
>     void generate_ddx(fs_inst *inst, struct brw_reg dst, struct brw_reg src);<br>
> -   void generate_ddy(fs_inst *inst, struct brw_reg dst, struct brw_reg src);<br>
> +   void generate_ddy(fs_inst *inst, struct brw_reg dst, struct brw_reg src,<br>
> +                     bool negate_value);<br>
>     void generate_spill(fs_inst *inst, struct brw_reg src);<br>
>     void generate_unspill(fs_inst *inst, struct brw_reg dst);<br>
>     void generate_pull_constant_load(fs_inst *inst, struct brw_reg dst);<br>
> diff --git a/src/mesa/drivers/dri/i965/brw_fs_emit.cpp b/src/mesa/drivers/dri/i965/brw_fs_emit.cpp<br>
> index 522123c..0881ad7 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_fs_emit.cpp<br>
> +++ b/src/mesa/drivers/dri/i965/brw_fs_emit.cpp<br>
> @@ -441,8 +441,13 @@ fs_visitor::generate_ddx(fs_inst *inst, struct brw_reg dst, struct brw_reg src)<br>
>     brw_ADD(p, dst, src0, negate(src1));<br>
>  }<br>
><br>
> +/* The negate_value boolean is used to negate the derivative computation for<br>
> + * FBOs, since they place the origin at the upper left instead of the lower<br>
> + * left.<br>
> + */<br>
>  void<br>
> -fs_visitor::generate_ddy(fs_inst *inst, struct brw_reg dst, struct brw_reg src)<br>
> +fs_visitor::generate_ddy(fs_inst *inst, struct brw_reg dst, struct brw_reg src,<br>
> +                         bool negate_value)<br>
>  {<br>
>     struct brw_reg src0 = brw_reg(src.file, <a href="http://src.nr" target="_blank">src.nr</a>, 0,<br>
>                                BRW_REGISTER_TYPE_F,<br>
> @@ -456,7 +461,10 @@ fs_visitor::generate_ddy(fs_inst *inst, struct brw_reg dst, struct brw_reg src)<br>
>                                BRW_WIDTH_4,<br>
>                                BRW_HORIZONTAL_STRIDE_0,<br>
>                                BRW_SWIZZLE_XYZW, WRITEMASK_XYZW);<br>
> -   brw_ADD(p, dst, src0, negate(src1));<br>
> +   if (negate_value)<br>
> +      brw_ADD(p, dst, src1, negate(src0));<br>
> +   else<br>
> +      brw_ADD(p, dst, src0, negate(src1));<br>
>  }<br>
><br>
>  void<br>
> @@ -902,7 +910,7 @@ fs_visitor::generate_code()<br>
>        generate_ddx(inst, dst, src[0]);<br>
>        break;<br>
>        case FS_OPCODE_DDY:<br>
> -      generate_ddy(inst, dst, src[0]);<br>
> +      generate_ddy(inst, dst, src[0], c->key.render_to_fbo);<br>
>        break;<br>
><br>
>        case FS_OPCODE_SPILL:<br>
> diff --git a/src/mesa/drivers/dri/i965/brw_wm.c b/src/mesa/drivers/dri/i965/brw_wm.c<br>
> index e919e5a..300e3bb 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_wm.c<br>
> +++ b/src/mesa/drivers/dri/i965/brw_wm.c<br>
> @@ -420,6 +420,13 @@ static void brw_wm_populate_key( struct brw_context *brw,<br>
>     GLuint line_aa;<br>
>     GLuint i;<br>
><br>
> +   /* As a temporary measure we assume that all programs use dFdy() (and hence<br>
> +    * need to be compiled differently depending on whether we're rendering to<br>
> +    * an FBO).  FIXME: set this bool correctly based on the contents of the<br>
> +    * program.<br>
> +    */<br>
> +   bool program_uses_dfdy = true;<br>
> +<br>
>     memset(key, 0, sizeof(*key));<br>
><br>
>     /* Build the index for table lookup<br>
> @@ -519,6 +526,9 @@ static void brw_wm_populate_key( struct brw_context *brw,<br>
>      */<br>
>     if (fp->program.Base.InputsRead & FRAG_BIT_WPOS) {<br>
>        key->drawable_height = ctx->DrawBuffer->Height;<br>
> +   }<br>
> +<br>
> +   if ((fp->program.Base.InputsRead & FRAG_BIT_WPOS) || program_uses_dfdy) {<br>
>        key->render_to_fbo = _mesa_is_user_fbo(ctx->DrawBuffer);<br>
>     }<br>
><br>
> diff --git a/src/mesa/drivers/dri/i965/brw_wm.h b/src/mesa/drivers/dri/i965/brw_wm.h<br>
> index 8f1cb8c..2cde2a0 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_wm.h<br>
> +++ b/src/mesa/drivers/dri/i965/brw_wm.h<br>
> @@ -346,7 +346,8 @@ void emit_ddxy(struct brw_compile *p,<br>
>              const struct brw_reg *dst,<br>
>              GLuint mask,<br>
>              bool is_ddx,<br>
> -            const struct brw_reg *arg0);<br>
> +            const struct brw_reg *arg0,<br>
> +            bool negate_value);<br>
>  void emit_delta_xy(struct brw_compile *p,<br>
>                  const struct brw_reg *dst,<br>
>                  GLuint mask,<br>
> diff --git a/src/mesa/drivers/dri/i965/brw_wm_emit.c b/src/mesa/drivers/dri/i965/brw_wm_emit.c<br>
> index e27ff35..1258efe 100644<br>
> --- a/src/mesa/drivers/dri/i965/brw_wm_emit.c<br>
> +++ b/src/mesa/drivers/dri/i965/brw_wm_emit.c<br>
> @@ -457,12 +457,16 @@ void emit_frontfacing(struct brw_compile *p,<br>
>   * between each other.  We could probably do it like ddx and swizzle the right<br>
>   * order later, but bail for now and just produce<br>
>   * ((<a href="http://ss0.tl" target="_blank">ss0.tl</a> - ss0.bl)x4 (<a href="http://ss1.tl" target="_blank">ss1.tl</a> - ss1.bl)x4)<br>
> + *<br>
> + * The negate_value boolean is used to negate the d/dy computation for FBOs,<br>
> + * since they place the origin at the upper left instead of the lower left.<br>
>   */<br>
>  void emit_ddxy(struct brw_compile *p,<br>
>              const struct brw_reg *dst,<br>
>              GLuint mask,<br>
>              bool is_ddx,<br>
> -            const struct brw_reg *arg0)<br>
> +            const struct brw_reg *arg0,<br>
> +               bool negate_value)<br>
>  {<br>
>     int i;<br>
>     struct brw_reg src0, src1;<br>
> @@ -498,7 +502,10 @@ void emit_ddxy(struct brw_compile *p,<br>
>                          BRW_HORIZONTAL_STRIDE_0,<br>
>                          BRW_SWIZZLE_XYZW, WRITEMASK_XYZW);<br>
>        }<br>
> -      brw_ADD(p, dst[i], src0, negate(src1));<br>
> +         if (negate_value)<br>
> +            brw_ADD(p, dst[i], src1, negate(src0));<br>
> +         else<br>
> +            brw_ADD(p, dst[i], src0, negate(src1));<br>
>        }<br>
>     }<br>
>     if (mask & SATURATE)<br>
> @@ -1746,11 +1753,11 @@ void brw_wm_emit( struct brw_wm_compile *c )<br>
>        break;<br>
><br>
>        case OPCODE_DDX:<br>
> -      emit_ddxy(p, dst, dst_flags, true, args[0]);<br>
> +      emit_ddxy(p, dst, dst_flags, true, args[0], false);<br>
>        break;<br>
><br>
>        case OPCODE_DDY:<br>
> -      emit_ddxy(p, dst, dst_flags, false, args[0]);<br>
> +      emit_ddxy(p, dst, dst_flags, false, args[0], c->key.render_to_fbo);<br>
>        break;<br>
><br>
>        case OPCODE_DP2:<br>
><br>
<br>
</div></div>This seems like a sensible solution to the problem at hand.<br>
<br>
Reviewed-by: Kenneth Graunke <<a href="mailto:kenneth@whitecape.org" target="_blank">kenneth@whitecape.org</a>><br>
<br>
It would be nice to not have to set this in the key universally, though,<br>
as this could have a pretty big impact on actual applications (twice the<br>
compiles, blowing out the program cache, etc.).  Hate to pull out the<br>
big hammer for a minor bug.  Of course, without measuring the impact on<br>
applications, I'm just wildly guessing :)  Perhaps I can check a few<br>
apps tomorrow.<br></blockquote><div><br></div><div>I will be curious to hear what you find--I suspect that it's rare for apps to re-use the same fragment shader on both FBOs and the main window (and when they do, I suspect they only do this for small shaders that are inexpensive to recompile).  But of course I'm guessing too :).  If you do find a slowdown, that will be further motivation to get cracking on the follow-up patch, so that we only do the recompile if the shader actually uses dFdy().</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
I wonder, actually, whether compiling two (four) entirely separate<br>
copies of the shader is really the best solution to this problem.  It<br>
might be best to simply add an implicit "uniform bool render_to_fbo" to<br>
any shaders that use dFdy() and emit two conditional ADDs, one for the<br>
FBO case and one for non-FBO.  This would add pretty minimal overhead to<br>
the shader, but be a lot nicer in terms of compile time and program<br>
cache usage. </blockquote><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Of course, that's probably harder, so I'm not suggesting it in the short<br>
term...more an idea to think about down the road.<br>
</blockquote></div><br>
<div>Yeah, I've been thinking about this too.  It seems like a good case to pilot test this idea with would be key.drawable_height, since that one could in pathological cases lead to an awful lot of recompiles.</div>