Closed Bug 1048724 Opened 10 years ago Closed 9 years ago

WebGL2 - Implement WebGLTransformFeedback

Categories

(Core :: Graphics: CanvasWebGL, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED

People

(Reporter: u480271, Assigned: u480271)

References

()

Details

(Keywords: dev-doc-complete)

Attachments

(9 files, 2 obsolete files)

Implement functions and types to support WebGLTransformFeedback object.

https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.5
https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
Summary: WebGL - Implement WebGLTransformFeedback → WebGL2 - Implement WebGLTransformFeedback
Keywords: leave-open
Attachment #8494093 - Flags: review?(jgilbert)
Attachment #8494093 - Flags: review?(jgilbert) → review+
Attachment #8527389 - Flags: review?(jgilbert)
Comment on attachment 8527389 [details] [diff] [review]
[WebGL2] Transform Feedback

Review of attachment 8527389 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ +54,5 @@
> +        return false;
> +
> +    MakeContextCurrent();
> +    return ValidateObjectAllowDeleted("isTransformFeedback", tf) &&
> +        !tf->IsDeleted() &&

Align with the beginning of Validate[...]

@@ +109,5 @@
> +
> +    // GL_INVALID_OPERATION is generated by glBeginTransformFeedback
> +    // if any binding point used in transform feedback mode does not
> +    // have a buffer object bound. In interleaved mode, only the first
> +    // buffer object binding point is ever written to.

We should generate INVALID_OP instead of MOZ_ASSERT(tf), it seems.

@@ +133,3 @@
>  
> +    WebGLTransformFeedback* tf = mBoundTransformFeedback;
> +    MOZ_ASSERT(tf);

Isn't this JS-facing? Shouldn't this be INVALID_OP like above?

@@ +151,5 @@
> +    if (IsContextLost())
> +        return;
> +
> +    WebGLTransformFeedback* tf = mBoundTransformFeedback;
> +    MOZ_ASSERT(tf);

Isn't this JS-facing? Shouldn't this be INVALID_OP like above?

@@ +170,5 @@
> +    if (IsContextLost())
> +        return;
> +
> +    WebGLTransformFeedback* tf = mBoundTransformFeedback;
> +    MOZ_ASSERT(tf);

Isn't this JS-facing? Shouldn't this be INVALID_OP like above?

@@ +225,5 @@
> +    gl->fGetProgramiv(progname, LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &len);
> +    if (!len)
> +        return nullptr;
> +
> +    nsAutoArrayPtr<char> name(new char[len]);

UniquePtr<char[]>

::: dom/canvas/WebGLContext.h
@@ +103,5 @@
> +typedef WebGLRefPtr<WebGLFramebuffer> WebGLFramebufferRefPtr;
> +typedef WebGLRefPtr<WebGLRenderbuffer> WebGLRenderbufferRefPtr;
> +typedef WebGLRefPtr<WebGLTexture> WebGLTextureRefPtr;
> +typedef WebGLRefPtr<WebGLTransformFeedback> WebGLTransformFeedbackRefPtr;
> +typedef WebGLRefPtr<WebGLVertexArray> WebGLVertexArrayRefPtr;

These don't seem like enough of an improvement in verbosity to be worth the tradeoff in opacity. The last thing we need is more non-trivial pointer types.

::: dom/canvas/WebGLContextBuffers.cpp
@@ +71,2 @@
>  
> +    WebGLBufferRefPtr* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index);

Where did this type come from? Why do we need it? Isn't it just RefPtr<WebGLBuffer>?

@@ +459,3 @@
>      switch (target) {
> +    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
> +        return &mBoundTransformFeedbackBuffers[index];

Assert `index` is within range.

@@ +462,3 @@
>  
> +    default:
> +        return nullptr;

It it's already validated, it shouldn't need a default case.
This should be a MOZ_CRASH, probably.

::: dom/canvas/WebGLTransformFeedback.h
@@ +36,5 @@
> +    GLenum Mode() const { return mMode; }
> +
> +    void SetActive(bool active) { mIsActive = active; }
> +    void SetPaused(bool paused) { mIsPaused = paused; }
> +    void SetMode(GLenum mode) { mMode = mode; }

If we have read and write access to a member, why not just make it public?
Attachment #8527389 - Flags: review?(jgilbert) → review-
(In reply to Jeff Gilbert [:jgilbert] from comment #5)
> Comment on attachment 8527389 [details] [diff] [review]
> [WebGL2] Transform Feedback
> @@ +109,5 @@

> > +
> > +    // GL_INVALID_OPERATION is generated by glBeginTransformFeedback
> > +    // if any binding point used in transform feedback mode does not
> > +    // have a buffer object bound. In interleaved mode, only the first
> > +    // buffer object binding point is ever written to.
> 
> We should generate INVALID_OP instead of MOZ_ASSERT(tf), it seems.

So Transform Feedback has a default object that is bound initially and whenever BindTransformFeedback is called with 0. (es_spec_3.0.4.pdf, page 87.)

The assertion is here to catch errors in the logic that binds to mDefaultTransformFeedback to mBoundTransformFeedback.
Address review comments
Attachment #8528675 - Flags: review?(jgilbert)
Attachment #8528675 - Flags: review?(jgilbert) → review+
Attachment #8529419 - Flags: review?(jgilbert)
Comment on attachment 8529419 [details] [diff] [review]
[WebGL2] Don't deref null mBoundTransformFeedbackBuffers

Review of attachment 8529419 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/canvas/WebGLContext.cpp
@@ +359,5 @@
>      mBoundTransformFeedback = nullptr;
>      mDefaultTransformFeedback = nullptr;
>  
> +    // mBoundTransformFeedbackBuffers is NULL in WebGL1
> +    if (mBoundTransformFeedbackBuffers)

{} around multi-line expressions for conditionals.
Attachment #8529419 - Flags: review?(jgilbert) → review+
Comment on attachment 8527389 [details] [diff] [review]
[WebGL2] Transform Feedback

Webidl was incorrect:
 * bindTransformFeedback, and
 * transformFeedbackVaryings

Asking :smaug for review of changes to .webidl
Attachment #8527389 - Flags: review?(bugs)
Comment on attachment 8527389 [details] [diff] [review]
[WebGL2] Transform Feedback

So it is ok to make not-backwards-compatible changes here?
I assume here the spec is what we want, so r+ for .webidl
Attachment #8527389 - Flags: review?(bugs) → review+
(In reply to Olli Pettay [:smaug] from comment #11)
> Comment on attachment 8527389 [details] [diff] [review]
> [WebGL2] Transform Feedback
> 
> So it is ok to make not-backwards-compatible changes here?

Yes, this is not exposed yet (it's hidden behind a pref). What we had didn't match the spec for WebGL 2, so I updated it.  Thanks.
I dont know what parts of it landed in which branch (35, apparently), but with 35.0b1 on OpenBSD, at startup i'm getting annoying warnings on stderr:

Can't find symbol 'GenTransformFeedbacksNV'.
Can't find symbol 'DeleteTransformFeedbacksNV'.
Can't find symbol 'IsTransformFeedbackNV'.
Can't find symbol 'PauseTransformFeedbackNV'.
Can't find symbol 'ResumeTransformFeedbackNV'.

Would be nice to have those silenced.....
Attached patch Transform Feeback Varyings. (obsolete) — Splinter Review
plus bug fixes.
Attachment #8583573 - Flags: review?(jgilbert)
Comment on attachment 8583573 [details] [diff] [review]
Transform Feeback Varyings.

Review of attachment 8583573 [details] [diff] [review]:
-----------------------------------------------------------------

glTransformFeedbackVaryings is like glBindAttribLocation, and glGetTransformFeedbackVaryings is like glGetActiveAttrib. We'll need to gather the ActiveInfo for the TF varyings in LinkInfo. We also need to add the checks that TF vars add to program linking.

::: dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ +204,5 @@
>  
>      if (!ValidateObject("transformFeedbackVaryings: program", program))
>          return;
>  
> +    program->TransformFeedbackVaryings(varyings, bufferMode);

Thanks for moving these into `program`.

::: dom/canvas/WebGLContextBuffers.cpp
@@ +92,5 @@
>      case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
>          if (index >= mGLMaxTransformFeedbackSeparateAttribs)
>              return ErrorInvalidValue("bindBufferBase: index should be less than "
>                                       "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
> +		break;

Why are we breaking after return?

@@ +500,5 @@
>      case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
>          MOZ_ASSERT(index < mGLMaxTransformFeedbackSeparateAttribs);
>          return mBoundTransformFeedbackBuffers[index];
> +
> +	case LOCAL_GL_UNIFORM_BUFFER:

Weird. Did your editor do some auto-formatting in this file?

::: dom/canvas/WebGLProgram.cpp
@@ +718,5 @@
> +void
> +WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
> +                                        GLenum bufferMode)
> +{
> +    // Spec doesn't say what to do if no vertex shader on program.

glTransformFeedbackVaryings is like glBindAttribLocation, in that it only applies at link time. It's perfectly normal to not have a vert shader at this stage, though one is required at link time.

As such, really, we should just copy (`varyings`, `bufferMode`) into some (`UniquePtr<nsCString[]> mTFVars`, `mTFBufferMode`) for later use at link time.

See GLES 3.0.4 p74 for the ways glTransformFeedbackVaryings affects linking.

@@ +726,5 @@
> +    const GLsizei varyingCount = varyings.Length();
> +    // Allocated temporary storage for converted strings. 257 elements per string to allow
> +    // for maximum name and \0 character.
> +    mVaryings = MakeUnique<GLchar[]>(varyingCount*(WEBGL_MAX_SHADER_NAME_LENGTH+1));
> +    UniquePtr<GLchar*[]> varyingPtrs = MakeUnique<GLchar*[]>(varyingCount);

It looks like we'd be better off skipping a copy and just having a UniquePtr<nsCString[]>, and filling varyingPtrs with mVaryings[i].BeginReading().

We'd be able to pass &mVaryings[i] into FindAttribMappedNameByUserName directly.

@@ +729,5 @@
> +    mVaryings = MakeUnique<GLchar[]>(varyingCount*(WEBGL_MAX_SHADER_NAME_LENGTH+1));
> +    UniquePtr<GLchar*[]> varyingPtrs = MakeUnique<GLchar*[]>(varyingCount);
> +
> +    GLchar* ptr = mVaryings.get();
> +    for (GLsizei n = 0; n < varyingCount; n++) {

Can we stick to `i` for iteration?

@@ +730,5 @@
> +    UniquePtr<GLchar*[]> varyingPtrs = MakeUnique<GLchar*[]>(varyingCount);
> +
> +    GLchar* ptr = mVaryings.get();
> +    for (GLsizei n = 0; n < varyingCount; n++) {
> +        NS_LossyConvertUTF16toASCII varyingName(varyings[n]);

We need to check that each varying is a valid GLSL string before doing this conversion.

@@ +755,5 @@
> +{
> +    nsRefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
> +
> +    if (index >= mTransformVaryingCount)
> +    {

{ on previous line

@@ +780,5 @@
> +    GLint tfsize = 0;
> +    GLuint tftype = 0;
> +
> +    gl->fGetTransformFeedbackVarying(mGLName, index, len, &len, &tfsize, &tftype,
> +                                     name.get());

This shouldn't be necessary, since we gather this info in LinkInfo. Just index into our local store of this info.

@@ +786,5 @@
> +        return ret.forget();
> +
> +    const auto& activeList = mMostRecentLinkInfo->activeAttribs;
> +    for (size_t n = 0; n < activeList.size(); n++) {
> +        if (activeList[n]->mBaseMappedName == name.get()) {

Does this work? Transform feedback vars aren't attribs. Attribs are 'in vars` in the vertex shader, whereas TF vars are 'out vars' in the vertex shader.

@@ +792,5 @@
> +            return ret.forget();
> +        }
> +    }
> +
> +    MOZ_ASSERT(false, "Shouldn't get here.");

IMO tend towards MOZ_CRASH here, so we can learn about this and fix it if it ends up in the wild.
Attachment #8583573 - Flags: review?(jgilbert) → review-
Blocks: 1153386
The webidl and spec was wrong wrt returnedData param and the language
regarding its usage.
Attachment #8593729 - Flags: review?(jgilbert)
Attachment #8593729 - Flags: review?(bugs)
Attachment #8593729 - Flags: review?(bugs) → review+
Attachment #8595061 - Flags: review?(jgilbert)
Attachment #8595061 - Flags: review?(jgilbert) → review+
Comment on attachment 8593729 [details] [diff] [review]
Implement GetBufferSubData. r=jgilbert, smaug

Review of attachment 8593729 [details] [diff] [review]:
-----------------------------------------------------------------

Let's get this in.

::: dom/canvas/WebGL2ContextBuffers.cpp
@@ +175,5 @@
> +    if (maybeData.IsNull())
> +        return ErrorInvalidValue("getBufferSubData: returnedData is null");
> +
> +    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
> +    WebGLBuffer* boundBuffer = bufferSlot.get();

`boundBuffer` seems superfluous to `bufferSlot`.

@@ +216,5 @@
> +        // objects are not active, but neither GLES3.0 nor OpenGL 4.5
> +        // spec guarantees this - just being bound for transform
> +        // feedback is sufficient to cause undefined results.
> +
> +        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);

I think we should INVALID_OP in this case, if we don't want the undefined results. If we're fine with undefined results, we should allow this without resetting the TF binding.

We should really not hide something like this from the app.
Attachment #8593729 - Flags: review?(jgilbert) → review+
Blocks: 1156589
(In reply to Jeff Gilbert [:jgilbert] from comment #20)
> @@ +216,5 @@
> > +        // objects are not active, but neither GLES3.0 nor OpenGL 4.5
> > +        // spec guarantees this - just being bound for transform
> > +        // feedback is sufficient to cause undefined results.
> > +
> > +        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
> 
> I think we should INVALID_OP in this case, if we don't want the undefined
> results. If we're fine with undefined results, we should allow this without
> resetting the TF binding.
> 
> We should really not hide something like this from the app.

The discussion at https://github.com/NVIDIA/WebGL/commit/63aff5e58c1d79825a596f0f4aa46174b9a5f72c, which is on a PR to the WebGL 2 spec by Olli from nVidia, pretty much says that's what we're supposed to do. From paragraph 2:

"Address this by adding an error for getBufferSubData when the buffer is
bound to a transform feedback binding point and any transform feedback
object is active. With this error in place, an underlying OpenGL-based
WebGL implementation can ensure correct behavior by unbinding the buffer
from transform feedback prior to mapping it for reading, and then binding
it again for transform feedback once being done with reading."

Do you want me to bring this up on the public mailing list?
Flags: needinfo?(jgilbert)
Attachment #8583573 - Attachment is obsolete: true
Attachment #8595717 - Flags: review?(jgilbert)
Attachment #8595717 - Attachment is obsolete: true
Attachment #8595717 - Flags: review?(jgilbert)
Attachment #8595721 - Flags: review?(jgilbert)
Comment on attachment 8595721 [details] [diff] [review]
Sort out Transform Feedback Varyings. r=jgilbert

Review of attachment 8595721 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/canvas/WebGLContextBuffers.cpp
@@ +92,5 @@
>      case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
>          if (index >= mGLMaxTransformFeedbackSeparateAttribs)
>              return ErrorInvalidValue("bindBufferBase: index should be less than "
>                                       "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
> +        break;

Oops! Good catch.

::: dom/canvas/WebGLProgram.cpp
@@ +294,5 @@
>  
>  WebGLProgram::WebGLProgram(WebGLContext* webgl)
>      : WebGLContextBoundObject(webgl)
>      , mGLName(CreateProgram(webgl->GL()))
> +    , mTransformFeedbackVaryings()

This is optional, isn't it? This would happen regardless of if we include it explicitly in the initializer list.

@@ +910,5 @@
>  
> +    // Post link, temporary mapped varying names for transform feedback can be discarded.
> +    // The memory can only be deleted after log is queried or the link status will fail.
> +    std::vector<std::string> empty;
> +    mTempMappedVaryings.swap(empty);

Interesting use of RAII. :)

@@ +960,5 @@
> +    {
> +        mContext->ErrorInvalidEnum("transformFeedbackVaryings: `bufferMode` %s is "
> +                                   "invalid. Must be one of gl.INTERLEAVED_ATTRIBS or "
> +                                   "gl.SEPARATE_ATTRIBS.",
> +                                   mContext->EnumName(bufferMode));

I generally prefer using switch/case statements for these, but for two this is ok.

@@ +974,5 @@
> +        return;
> +    }
> +
> +    mTransformFeedbackBufferMode = bufferMode;
> +    mTransformFeedbackVaryings.clear();

Hold up! Can't clear this until we're done with error checking, which we still do below in the loop! (r-)

@@ +1006,5 @@
> +
> +    const nsCString& varyingUserName = mTransformFeedbackVaryings[index];
> +
> +    WebGLActiveInfo* info;
> +    LinkInfo()->FindAttrib(varyingUserName, (const WebGLActiveInfo**) &info);

Oh, I guess this shouldn't be const if we're just overriding it here.

::: dom/canvas/WebGLShader.cpp
@@ +356,5 @@
>  
> +void
> +WebGLShader::TransformFeedbackVaryings(GLuint prog,
> +                                       const std::vector<nsCString>& varyings,
> +                                       std::vector<std::string>& mappedVaryings,

`foo* out_bar`, put at end of arg list, no?
Attachment #8595721 - Flags: review?(jgilbert) → review-
(In reply to Jeff Gilbert [:jgilbert] from comment #28)
> ::: dom/canvas/WebGLProgram.cpp
> @@ +294,5 @@
> >  
> >  WebGLProgram::WebGLProgram(WebGLContext* webgl)
> >      : WebGLContextBoundObject(webgl)
> >      , mGLName(CreateProgram(webgl->GL()))
> > +    , mTransformFeedbackVaryings()
> 
> This is optional, isn't it? This would happen regardless of if we include it
> explicitly in the initializer list.

Yes. Remove it?

> @@ +910,5 @@
> > +    std::vector<std::string> empty;
> > +    mTempMappedVaryings.swap(empty);
> 
> Interesting use of RAII. :)

This called the "swap trick". I learnt from Scott Meyers' Effective STL.
 
> @@ +974,5 @@
> > +        return;
> > +    }
> > +
> > +    mTransformFeedbackBufferMode = bufferMode;
> > +    mTransformFeedbackVaryings.clear();
> 
> Hold up! Can't clear this until we're done with error checking, which we
> still do below in the loop! (r-)

Right. Will fix.

> @@ +1006,5 @@
> > +
> > +    const nsCString& varyingUserName = mTransformFeedbackVaryings[index];
> > +
> > +    WebGLActiveInfo* info;
> > +    LinkInfo()->FindAttrib(varyingUserName, (const WebGLActiveInfo**) &info);
> 
> Oh, I guess this shouldn't be const if we're just overriding it here.
> 
> ::: dom/canvas/WebGLShader.cpp
> @@ +356,5 @@
> >  
> > +void
> > +WebGLShader::TransformFeedbackVaryings(GLuint prog,
> > +                                       const std::vector<nsCString>& varyings,
> > +                                       std::vector<std::string>& mappedVaryings,
> 
> `foo* out_bar`, put at end of arg list, no?

Can I keep the reference and prepend out_?
(In reply to Dan Glastonbury :djg :kamidphish from comment #30)
> Created attachment 8595763 [details] [diff] [review]
> Address transform feedback varyings review comments. r=jgilbert

https://treeherder.mozilla.org/#/jobs?repo=try&revision=1d62994d5a10 -
Woah, something bad happening.
Comment on attachment 8595763 [details] [diff] [review]
Address transform feedback varyings review comments. r=jgilbert

Review of attachment 8595763 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/canvas/WebGLProgram.cpp
@@ +908,5 @@
>      }
>  
>      // Post link, temporary mapped varying names for transform feedback can be discarded.
>      // The memory can only be deleted after log is queried or the link status will fail.
> +    std::vector<std::string>().swap(mTempMappedVaryings);

I don't think this does what you want, does it? It'll create an unused temporary that'll be immediately destroyed, taking the old mTempMappedVaryings with it. This looks like it's the same as just calling `.clear()`.

I think you need to swap it with a stack-allocated var to get the RAII you want.

r- for this, but r+ if you revert this change. (leaving as r+ for expediency)

@@ +982,2 @@
>      mTransformFeedbackBufferMode = bufferMode;
>      mTransformFeedbackVaryings.clear();

We can technically do this in one pass by using a stack var and swapping it into mTransformFeedbackVaryings at the end, if there's no error, I think.
Attachment #8595763 - Flags: review?(jgilbert) → review+
(In reply to Jeff Gilbert [:jgilbert] from comment #32)
> Comment on attachment 8595763 [details] [diff] [review]
> Address transform feedback varyings review comments. r=jgilbert
> 
> Review of attachment 8595763 [details] [diff] [review]:
> -----------------------------------------------------------------
> 
> ::: dom/canvas/WebGLProgram.cpp
> @@ +908,5 @@
> >      }
> >  
> >      // Post link, temporary mapped varying names for transform feedback can be discarded.
> >      // The memory can only be deleted after log is queried or the link status will fail.
> > +    std::vector<std::string>().swap(mTempMappedVaryings);
> 
> I don't think this does what you want, does it? It'll create an unused
> temporary that'll be immediately destroyed, taking the old
> mTempMappedVaryings with it. This looks like it's the same as just calling
> `.clear()`.
> 
> I think you need to swap it with a stack-allocated var to get the RAII you
> want.

See Item 17 of Effective C++ (http://www.uml.org.cn/c++/pdf/EffectiveSTL.pdf).  The example that is used is:

vector<Contestant>(contestants).swap(contestants);

Which creates a new temporary vector, copies the elements of contestants, then swaps it with contestants. The temporary then destructs, freeing memory allocated to the old contestants.  This works because the temporary is alive until the next sequence point which, in this case, is at the end of the statement.

I'm creating an empty vector, swapping it with mTempMappedVaryings so that the vector becomes mTempMappedVaryings and mTempMappedVaryings a default vector. This is so the memory allocated by mTempMappedVaryings is freed, because `clear()` only resets internal size, it doesn't set capacity to zero and free the memory.

I see that C++11 has `shrink_to_fit()`. We're starting to use C++11 features in the code, so maybe I can use that instead.

> r- for this, but r+ if you revert this change. (leaving as r+ for expediency)
> 
> @@ +982,2 @@
> >      mTransformFeedbackBufferMode = bufferMode;
> >      mTransformFeedbackVaryings.clear();
> 
> We can technically do this in one pass by using a stack var and swapping it
> into mTransformFeedbackVaryings at the end, if there's no error, I think.

Yes, I think I'll do that.
Attachment #8596299 - Flags: review?(jgilbert)
Flags: needinfo?(jgilbert)
Comment on attachment 8596299 [details] [diff] [review]
2nd set of review comments + bug fix. r=jgilbert

Review of attachment 8596299 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/canvas/WebGLShader.cpp
@@ +388,5 @@
>  
>      mContext->MakeContextCurrent();
>      mContext->gl->fTransformFeedbackVaryings(prog, varyingsCount, &strings[0], bufferMode);
> +
> +    out_mappedVaryings->swap(mappedVaryings);

Swap all the things, eh? :)
Attachment #8596299 - Flags: review?(jgilbert) → review+
Keywords: leave-open
Status: NEW → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: