зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1310247 - Check if buffers are bound for transform feedback. - r=ethlin
MozReview-Commit-ID: 4EsOdz7vXNH
This commit is contained in:
Родитель
ece5f74086
Коммит
38330f393b
|
@ -31,15 +31,6 @@ WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
|
|||
if (!writeBuffer)
|
||||
return;
|
||||
|
||||
if (readBuffer->mNumActiveTFOs ||
|
||||
writeBuffer->mNumActiveTFOs)
|
||||
{
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateNonNegative(funcName, "readOffset", readOffset) ||
|
||||
!ValidateNonNegative(funcName, "writeOffset", writeOffset) ||
|
||||
!ValidateNonNegative(funcName, "size", size))
|
||||
|
@ -129,21 +120,6 @@ WebGL2Context::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
|
|||
|
||||
////
|
||||
|
||||
if (buffer->mNumActiveTFOs) {
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
|
||||
mBoundTransformFeedback->mIsActive)
|
||||
{
|
||||
ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckedInt<GLsizeiptr>(byteLen).isValid()) {
|
||||
ErrorOutOfMemory("%s: Size too large.", funcName);
|
||||
return;
|
||||
|
|
|
@ -18,15 +18,12 @@ WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
|
|||
, mContent(Kind::Undefined)
|
||||
, mUsage(LOCAL_GL_STATIC_DRAW)
|
||||
, mByteLength(0)
|
||||
, mNumActiveTFOs(0)
|
||||
, mBoundForTF(false)
|
||||
{
|
||||
mContext->mBuffers.insertBack(this);
|
||||
}
|
||||
|
||||
WebGLBuffer::~WebGLBuffer()
|
||||
{
|
||||
MOZ_ASSERT(!mNumActiveTFOs);
|
||||
DeleteOnce();
|
||||
}
|
||||
|
||||
|
@ -111,13 +108,6 @@ WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usa
|
|||
if (!ValidateBufferUsageEnum(mContext, funcName, usage))
|
||||
return;
|
||||
|
||||
if (mNumActiveTFOs) {
|
||||
mContext->ErrorInvalidOperation("%s: Buffer is bound to an active transform"
|
||||
" feedback object.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& gl = mContext->gl;
|
||||
gl->MakeCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, target, this);
|
||||
|
@ -220,15 +210,6 @@ WebGLBuffer::IsElementArrayUsedWithMultipleTypes() const
|
|||
bool
|
||||
WebGLBuffer::ValidateCanBindToTarget(const char* funcName, GLenum target)
|
||||
{
|
||||
const bool wouldBeTF = (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||
if (mWebGLRefCnt && wouldBeTF != mBoundForTF) {
|
||||
mContext->ErrorInvalidOperation("%s: Buffers cannot be simultaneously bound to "
|
||||
" transform feedback and bound elsewhere.",
|
||||
funcName);
|
||||
return false;
|
||||
}
|
||||
mBoundForTF = wouldBeTF;
|
||||
|
||||
/* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
|
||||
*
|
||||
* In the WebGL 2 API, buffers have their WebGL buffer type
|
||||
|
|
|
@ -79,8 +79,6 @@ protected:
|
|||
GLenum mUsage;
|
||||
size_t mByteLength;
|
||||
UniquePtr<WebGLElementArrayCache> mCache;
|
||||
size_t mNumActiveTFOs;
|
||||
bool mBoundForTF;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -120,6 +120,7 @@ WebGLContext::WebGLContext()
|
|||
, mMaxFetchedInstances(0)
|
||||
, mLayerIsMirror(false)
|
||||
, mBypassShaderValidation(false)
|
||||
, mBuffersForUB_Dirty(true)
|
||||
, mContextLossHandler(this)
|
||||
, mNeedsFakeNoAlpha(false)
|
||||
, mNeedsFakeNoDepth(false)
|
||||
|
@ -262,6 +263,7 @@ WebGLContext::DestroyResourcesAndContext()
|
|||
mQuerySlot_TimeElapsed = nullptr;
|
||||
|
||||
mIndexedUniformBufferBindings.clear();
|
||||
OnUBIndexedBindingsChanged();
|
||||
|
||||
//////
|
||||
|
||||
|
@ -2570,6 +2572,42 @@ WebGLContext::ValidateArrayBufferView(const char* funcName,
|
|||
return true;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
const decltype(WebGLContext::mBuffersForUB)&
|
||||
WebGLContext::BuffersForUB() const
|
||||
{
|
||||
if (mBuffersForUB_Dirty) {
|
||||
mBuffersForUB.clear();
|
||||
for (const auto& cur : mIndexedUniformBufferBindings) {
|
||||
if (cur.mBufferBinding) {
|
||||
mBuffersForUB.insert(cur.mBufferBinding.get());
|
||||
}
|
||||
}
|
||||
mBuffersForUB_Dirty = false;
|
||||
}
|
||||
return mBuffersForUB;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
bool
|
||||
WebGLContext::ValidateForNonTransformFeedback(const char* funcName, WebGLBuffer* buffer)
|
||||
{
|
||||
if (!mBoundTransformFeedback)
|
||||
return true;
|
||||
|
||||
const auto& buffersForTF = mBoundTransformFeedback->BuffersForTF();
|
||||
if (buffersForTF.count(buffer)) {
|
||||
ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for transform"
|
||||
" feedback.",
|
||||
funcName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// XPCOM goop
|
||||
|
||||
|
|
|
@ -1580,7 +1580,9 @@ protected:
|
|||
}
|
||||
|
||||
WebGLRefPtr<WebGLBuffer>* ValidateBufferSlot(const char* funcName, GLenum target);
|
||||
public:
|
||||
WebGLBuffer* ValidateBufferSelection(const char* funcName, GLenum target);
|
||||
protected:
|
||||
IndexedBufferBinding* ValidateIndexedBufferSlot(const char* funcName, GLenum target,
|
||||
GLuint index);
|
||||
|
||||
|
@ -1596,6 +1598,8 @@ protected:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ValidateForNonTransformFeedback(const char* funcName, WebGLBuffer* buffer);
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
bool ValidateNonNull(const char* funcName, const dom::Nullable<T>& maybe) {
|
||||
|
@ -1754,6 +1758,17 @@ protected:
|
|||
|
||||
////////////////////////////////////
|
||||
|
||||
private:
|
||||
mutable bool mBuffersForUB_Dirty;
|
||||
mutable std::set<const WebGLBuffer*> mBuffersForUB;
|
||||
|
||||
public:
|
||||
void OnUBIndexedBindingsChanged() const { mBuffersForUB_Dirty = true; }
|
||||
const decltype(mBuffersForUB)& BuffersForUB() const;
|
||||
|
||||
////////////////////////////////////
|
||||
|
||||
protected:
|
||||
// Generic Vertex Attributes
|
||||
UniquePtr<GLenum[]> mVertexAttribType;
|
||||
GLfloat mVertexAttrib0Vector[4];
|
||||
|
|
|
@ -75,6 +75,9 @@ WebGLContext::ValidateBufferSelection(const char* funcName, GLenum target)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ValidateForNonTransformFeedback(funcName, buffer.get()))
|
||||
return nullptr;
|
||||
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
|
@ -212,6 +215,15 @@ WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
|
|||
if (buffer) {
|
||||
buffer->SetContentAfterBind(target);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
|
||||
mBoundTransformFeedback->OnIndexedBindingsChanged();
|
||||
break;
|
||||
case LOCAL_GL_UNIFORM:
|
||||
OnUBIndexedBindingsChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -296,6 +308,15 @@ WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
|
|||
if (buffer) {
|
||||
buffer->SetContentAfterBind(target);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
|
||||
mBoundTransformFeedback->OnIndexedBindingsChanged();
|
||||
break;
|
||||
case LOCAL_GL_UNIFORM:
|
||||
OnUBIndexedBindingsChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
|
@ -383,13 +404,6 @@ WebGLContext::BufferSubDataImpl(GLenum target, WebGLsizeiptr dstByteOffset,
|
|||
if (!buffer)
|
||||
return;
|
||||
|
||||
if (buffer->mNumActiveTFOs) {
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
"bufferSubData");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buffer->ValidateRange(funcName, dstByteOffset, dataLen))
|
||||
return;
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "WebGLVertexArray.h"
|
||||
#include "WebGLVertexAttribData.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// For a Tegra workaround.
|
||||
|
@ -300,6 +302,16 @@ WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first,
|
|||
|
||||
////////////////////////////////////////
|
||||
|
||||
template<typename T>
|
||||
static bool
|
||||
DoSetsIntersect(const std::set<T>& a, const std::set<T>& b)
|
||||
{
|
||||
std::vector<T> intersection;
|
||||
std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
|
||||
std::back_inserter(intersection));
|
||||
return bool(intersection.size());
|
||||
}
|
||||
|
||||
class ScopedDrawHelper final
|
||||
{
|
||||
WebGLContext* const mWebGL;
|
||||
|
@ -343,7 +355,7 @@ public:
|
|||
////
|
||||
// Check UBO sizes.
|
||||
|
||||
const auto& linkInfo = webgl->mActiveProgramLinkInfo;
|
||||
const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
|
||||
for (const auto& cur : linkInfo->uniformBlocks) {
|
||||
const auto& dataSize = cur->mDataSize;
|
||||
const auto& binding = cur->mBinding;
|
||||
|
@ -366,6 +378,22 @@ public:
|
|||
|
||||
////
|
||||
|
||||
const auto& tfo = mWebGL->mBoundTransformFeedback;
|
||||
if (tfo) {
|
||||
const auto& buffersForTF = tfo->BuffersForTF();
|
||||
const auto& buffersForUB = mWebGL->BuffersForUB();
|
||||
if (DoSetsIntersect(buffersForTF, buffersForUB)) {
|
||||
mWebGL->ErrorInvalidOperation("%s: At least one WebGLBuffer is bound for"
|
||||
" both transform feedback and as a uniform"
|
||||
" buffer.",
|
||||
funcName);
|
||||
*out_error = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
mWebGL->RunContextLossTimer();
|
||||
}
|
||||
|
||||
|
|
|
@ -1462,41 +1462,33 @@ void
|
|||
WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
|
||||
GLenum type, WebGLsizeiptr offset, ErrorResult& out_error)
|
||||
{
|
||||
const char funcName[] = "readPixels";
|
||||
if (!ReadPixels_SharedPrecheck(&out_error))
|
||||
return;
|
||||
|
||||
if (!mBoundPixelPackBuffer) {
|
||||
ErrorInvalidOperation("readPixels: PIXEL_PACK_BUFFER must not be null.");
|
||||
const auto& buffer = ValidateBufferSelection(funcName, LOCAL_GL_PIXEL_PACK_BUFFER);
|
||||
if (!buffer)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBoundPixelPackBuffer->mNumActiveTFOs) {
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
"readPixels");
|
||||
return;
|
||||
}
|
||||
|
||||
//////
|
||||
|
||||
if (offset < 0) {
|
||||
ErrorInvalidValue("readPixels: offset must not be negative.");
|
||||
if (!ValidateNonNegative(funcName, "offset", offset))
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const auto bytesPerType = webgl::BytesPerPixel({LOCAL_GL_RED, type});
|
||||
|
||||
if (offset % bytesPerType != 0) {
|
||||
ErrorInvalidOperation("readPixels: `offset` must be divisible by the size"
|
||||
" a `type` in bytes.");
|
||||
ErrorInvalidOperation("%s: `offset` must be divisible by the size of `type`"
|
||||
" in bytes.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//////
|
||||
|
||||
const auto bytesAvailable = mBoundPixelPackBuffer->ByteLength();
|
||||
const auto bytesAvailable = buffer->ByteLength();
|
||||
const auto checkedBytesAfterOffset = CheckedUint32(bytesAvailable) - offset;
|
||||
|
||||
uint32_t bytesAfterOffset = 0;
|
||||
|
@ -1505,7 +1497,7 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum
|
|||
}
|
||||
|
||||
gl->MakeCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, LOCAL_GL_PIXEL_PACK_BUFFER, mBoundPixelPackBuffer);
|
||||
const ScopedLazyBind lazyBind(gl, LOCAL_GL_PIXEL_PACK_BUFFER, buffer);
|
||||
|
||||
ReadPixelsImpl(x, y, width, height, format, type, (void*)offset, bytesAfterOffset);
|
||||
}
|
||||
|
|
|
@ -1444,7 +1444,7 @@ WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
|
|||
GLuint maxAttribs = 0;
|
||||
gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
||||
&maxAttribs);
|
||||
if (varyings.Length() >= maxAttribs) {
|
||||
if (varyings.Length() > maxAttribs) {
|
||||
mContext->ErrorInvalidValue("%s: Length of `varyings` exceeds %s.",
|
||||
funcName,
|
||||
"TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
|
||||
|
|
|
@ -185,14 +185,19 @@ FromView(WebGLContext* webgl, const char* funcName, TexImageTarget target,
|
|||
|
||||
static UniquePtr<webgl::TexUnpackBytes>
|
||||
FromPboOffset(WebGLContext* webgl, const char* funcName, TexImageTarget target,
|
||||
uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset,
|
||||
size_t availBufferBytes)
|
||||
uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset)
|
||||
{
|
||||
if (pboOffset < 0) {
|
||||
webgl->ErrorInvalidValue("%s: offset cannot be negative.", funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& buffer = webgl->ValidateBufferSelection(funcName,
|
||||
LOCAL_GL_PIXEL_UNPACK_BUFFER);
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
size_t availBufferBytes = buffer->ByteLength();
|
||||
if (size_t(pboOffset) > availBufferBytes) {
|
||||
webgl->ErrorInvalidOperation("%s: Offset is passed end of buffer.", funcName);
|
||||
return nullptr;
|
||||
|
@ -366,25 +371,12 @@ WebGLContext::From(const char* funcName, TexImageTarget target, GLsizei rawWidth
|
|||
}
|
||||
|
||||
if (src.mPboOffset) {
|
||||
if (!mBoundPixelUnpackBuffer) {
|
||||
ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
|
||||
return FromPboOffset(this, funcName, target, width, height, depth,
|
||||
*(src.mPboOffset), availBytes);
|
||||
*(src.mPboOffset));
|
||||
}
|
||||
|
||||
if (mBoundPixelUnpackBuffer) {
|
||||
ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
|
||||
ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1370,25 +1362,12 @@ WebGLContext::FromCompressed(const char* funcName, TexImageTarget target,
|
|||
}
|
||||
|
||||
if (src.mPboOffset) {
|
||||
if (!mBoundPixelUnpackBuffer) {
|
||||
ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
|
||||
ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
|
||||
" object.",
|
||||
funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
|
||||
return FromPboOffset(this, funcName, target, width, height, depth,
|
||||
*(src.mPboOffset), availBytes);
|
||||
*(src.mPboOffset));
|
||||
}
|
||||
|
||||
if (mBoundPixelUnpackBuffer) {
|
||||
ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
|
||||
ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ WebGLTransformFeedback::WebGLTransformFeedback(WebGLContext* webgl, GLuint tf)
|
|||
, mIndexedBindings(webgl->mGLMaxTransformFeedbackSeparateAttribs)
|
||||
, mIsPaused(false)
|
||||
, mIsActive(false)
|
||||
, mBuffersForTF_Dirty(true)
|
||||
{
|
||||
mContext->mTransformFeedbacks.insertBack(this);
|
||||
}
|
||||
|
@ -36,6 +37,28 @@ WebGLTransformFeedback::Delete()
|
|||
removeFrom(mContext->mTransformFeedbacks);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
const decltype(WebGLTransformFeedback::mBuffersForTF)&
|
||||
WebGLTransformFeedback::BuffersForTF() const
|
||||
{
|
||||
// The generic bind point cannot incur undefined read/writes because otherwise it
|
||||
// would be impossible to read back from this. The spec implies that readback from
|
||||
// the TRANSFORM_FEEDBACK target is possible, just not simultaneously with being
|
||||
// "bound or in use for transform feedback".
|
||||
// Therefore, only the indexed bindings of the TFO count.
|
||||
if (mBuffersForTF_Dirty) {
|
||||
mBuffersForTF.clear();
|
||||
for (const auto& cur : mIndexedBindings) {
|
||||
if (cur.mBufferBinding) {
|
||||
mBuffersForTF.insert(cur.mBufferBinding.get());
|
||||
}
|
||||
}
|
||||
mBuffersForTF_Dirty = false;
|
||||
}
|
||||
return mBuffersForTF;
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
void
|
||||
|
@ -107,13 +130,6 @@ WebGLTransformFeedback::BeginTransformFeedback(GLenum primMode)
|
|||
|
||||
////
|
||||
|
||||
for (const auto& cur : mIndexedBindings) {
|
||||
const auto& buffer = cur.mBufferBinding;
|
||||
if (buffer) {
|
||||
buffer->mNumActiveTFOs++;
|
||||
}
|
||||
}
|
||||
|
||||
mActive_Program->mNumActiveTFOs++;
|
||||
}
|
||||
|
||||
|
@ -139,13 +155,6 @@ WebGLTransformFeedback::EndTransformFeedback()
|
|||
|
||||
////
|
||||
|
||||
for (const auto& cur : mIndexedBindings) {
|
||||
const auto& buffer = cur.mBufferBinding;
|
||||
if (buffer) {
|
||||
buffer->mNumActiveTFOs--;
|
||||
}
|
||||
}
|
||||
|
||||
mActive_Program->mNumActiveTFOs--;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ private:
|
|||
MOZ_INIT_OUTSIDE_CTOR size_t mActive_VertPosition;
|
||||
MOZ_INIT_OUTSIDE_CTOR size_t mActive_VertCapacity;
|
||||
|
||||
mutable bool mBuffersForTF_Dirty;
|
||||
mutable std::set<const WebGLBuffer*> mBuffersForTF;
|
||||
|
||||
public:
|
||||
WebGLTransformFeedback(WebGLContext* webgl, GLuint tf);
|
||||
private:
|
||||
|
@ -50,6 +53,11 @@ public:
|
|||
WebGLContext* GetParentObject() const { return mContext; }
|
||||
virtual JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
|
||||
|
||||
////
|
||||
|
||||
void OnIndexedBindingsChanged() const { mBuffersForTF_Dirty = true; }
|
||||
const decltype(mBuffersForTF)& BuffersForTF() const;
|
||||
|
||||
// GL Funcs
|
||||
void BeginTransformFeedback(GLenum primMode);
|
||||
void EndTransformFeedback();
|
||||
|
|
Загрузка…
Ссылка в новой задаче