mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_Impl, MG_State): implement the direct state access transform feedback API
glCreateTransformFeedbacks, glTransformFeedbackBufferBase, glTransformFeedbackBufferRange and the three glGetTransformFeedback* queries were all stubs, so a transform feedback object could only be configured and inspected by binding it first - the exact thing direct state access exists to avoid. The queries were the worse half: they returned nothing and raised no error, so an application could not tell that it had learned nothing. glCreateTransformFeedbacks creates the objects outright. glGenTransformFeedbacks only reserves names, and a reserved name becomes an object when it is first bound (GL 4.6 core 13.2.1); the DSA form has no bind step to create them from. The queries and the buffer bindings read and write a named object's state. That state lives in two places: the context keeps one live copy of the capture bindings and the active/paused flags for whichever object is bound, and every other object's copy sits in its saved state until a bind swaps it in. The by-name accessors added to the context resolve that, so a query for the bound object reads the live copy rather than a stale save. GL_TRANSFORM_FEEDBACK_BUFFER_START and _SIZE are answered as zero unless the binding was made by the range form, matching what the buffer object binding points already do. Takes direct_state_access.xfb_* from 0 to 4 of 5 on both backends; xfb_functional still fails on the capture itself, which is a separate defect.
This commit is contained in:
@@ -877,6 +877,172 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
void CreateTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (n == 0 || ids == nullptr) return;
|
||||
Vector<Uint> names;
|
||||
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||
// Unlike glGenTransformFeedbacks, the names are objects immediately: there is no bind step
|
||||
// to create them from (GL 4.6 core 13.2.1).
|
||||
for (const Uint name : names) {
|
||||
MG_State::pGLContext->CreateTransformFeedbackObject(name);
|
||||
}
|
||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Shared front half of the by-name transform feedback entry points: the object has to exist
|
||||
// (INVALID_OPERATION otherwise) before anything else about the call is looked at.
|
||||
Bool ValidateNamedTransformFeedback(GLuint xfb, const char* functionName) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackObject(xfb)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
std::to_string(xfb) + " is not a transform feedback object."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateTransformFeedbackBufferIndex(GLuint index, const char* functionName) {
|
||||
if (index >= MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"index exceeds GL_MAX_TRANSFORM_FEEDBACK_BUFFERS."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A capture binding may not be changed while the object is capturing (GL 4.6 core 13.2.2).
|
||||
Bool ValidateNamedTransformFeedbackNotActive(GLuint xfb, const char* functionName) {
|
||||
if (MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"The transform feedback object is capturing."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::BufferObject> ResolveTransformFeedbackBuffer(GLuint buffer,
|
||||
const char* functionName) {
|
||||
if (buffer == 0) return nullptr;
|
||||
if (!MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return nullptr;
|
||||
}
|
||||
return MG_State::pGLContext->GetBufferObject(buffer);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index,
|
||||
ResolveTransformFeedbackBuffer(buffer, __func__), {},
|
||||
false);
|
||||
}
|
||||
|
||||
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||
if (offset < 0 || size <= 0 || (offset % 4) != 0 || (size % 4) != 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"offset and size must be non-negative multiples of 4."));
|
||||
return;
|
||||
}
|
||||
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(buffer) + " is not a buffer object."));
|
||||
return;
|
||||
}
|
||||
auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__);
|
||||
const Range1D range{static_cast<SizeT>(offset), static_cast<SizeT>(offset) + static_cast<SizeT>(size)};
|
||||
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, bufferObject, range,
|
||||
bufferObject != nullptr);
|
||||
}
|
||||
|
||||
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (!param) return;
|
||||
switch (pname) {
|
||||
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||
*param = MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||
*param = MG_State::pGLContext->IsNamedTransformFeedbackPaused(xfb) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_ACTIVE or _PAUSED."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_BINDING) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_BINDING."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!param) return;
|
||||
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||
*param = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||
}
|
||||
|
||||
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param) {
|
||||
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_START && pname != GL_TRANSFORM_FEEDBACK_BUFFER_SIZE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_START or _SIZE."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||
if (!param) return;
|
||||
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||
// glTransformFeedbackBufferBase leaves both at zero; only the range form sets them
|
||||
// (GL 4.6 core table 23.48).
|
||||
if (!binding.Buffer || !binding.HasExplicitRange) {
|
||||
*param = 0;
|
||||
return;
|
||||
}
|
||||
*param = (pname == GL_TRANSFORM_FEEDBACK_BUFFER_START)
|
||||
? static_cast<GLint64>(binding.Range.start)
|
||||
: static_cast<GLint64>(binding.Range.end - binding.Range.start);
|
||||
}
|
||||
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
|
||||
@@ -16,7 +16,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void PauseTransformFeedback(void);
|
||||
void ResumeTransformFeedback(void);
|
||||
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||
void CreateTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
||||
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer);
|
||||
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param);
|
||||
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param);
|
||||
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param);
|
||||
void BindTransformFeedback(GLenum target, GLuint id);
|
||||
GLboolean IsTransformFeedback(GLuint id);
|
||||
void DrawTransformFeedback(GLenum mode, GLuint id);
|
||||
|
||||
@@ -1007,12 +1007,12 @@ DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
||||
// For glBindBufferBase / glBindBufferRange
|
||||
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
|
||||
const BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index) const {
|
||||
return const_cast<BufferState*>(this)->GetBindingPoint(target, index);
|
||||
}
|
||||
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
|
||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
||||
|
||||
@@ -842,6 +842,64 @@ namespace MobileGL::MG_State {
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
|
||||
}
|
||||
|
||||
void GLContext::CreateTransformFeedbackObject(Uint index) {
|
||||
// glCreateTransformFeedbacks has no bind step to infer existence from, so the name it
|
||||
// hands out is already the name of an object (GL 4.6 core 13.2.1).
|
||||
m_transformFeedbackObjects[index] = {};
|
||||
m_transformFeedbackObjects[index].everBound = true;
|
||||
}
|
||||
|
||||
Bool GLContext::IsNamedTransformFeedbackActive(Uint index) const {
|
||||
if (index == m_boundTransformFeedback) return m_transformFeedbackActive;
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.active;
|
||||
}
|
||||
|
||||
Bool GLContext::IsNamedTransformFeedbackPaused(Uint index) const {
|
||||
if (index == m_boundTransformFeedback) return m_transformFeedbackPaused;
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.paused;
|
||||
}
|
||||
|
||||
NamedTransformFeedbackBinding GLContext::GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const {
|
||||
NamedTransformFeedbackBinding result;
|
||||
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return result;
|
||||
// The bound object's capture bindings live in the context's own binding points, not in
|
||||
// the saved copy - that one is only written when the object is swapped out.
|
||||
if (index == m_boundTransformFeedback) {
|
||||
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||
result.Buffer = point.GetBoundObject();
|
||||
result.Range = point.GetRange();
|
||||
result.HasExplicitRange = point.HasExplicitRange();
|
||||
return result;
|
||||
}
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
if (it == m_transformFeedbackObjects.end()) return result;
|
||||
const auto& saved = it->second.bindings[bufferIndex];
|
||||
result.Buffer = saved.buffer;
|
||||
result.Range = saved.range;
|
||||
result.HasExplicitRange = saved.hasExplicitRange;
|
||||
return result;
|
||||
}
|
||||
|
||||
void GLContext::SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||
Bool hasExplicitRange) {
|
||||
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return;
|
||||
if (index == m_boundTransformFeedback) {
|
||||
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||
point.Bind(buffer);
|
||||
if (buffer && hasExplicitRange) {
|
||||
point.SetRange(range, true);
|
||||
} else {
|
||||
point.ClearRange();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto& object = m_transformFeedbackObjects[index];
|
||||
object.bindings[bufferIndex] = {buffer, range, hasExplicitRange};
|
||||
}
|
||||
} // namespace GLState
|
||||
|
||||
// Leak-at-exit storage; see GlobalObjects.cpp.
|
||||
|
||||
@@ -45,6 +45,14 @@ namespace MobileGL {
|
||||
// translates the result into its own API call.
|
||||
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
|
||||
|
||||
// One indexed capture binding of a transform feedback object, as the by-name queries
|
||||
// report it. An empty Buffer means the binding point is unbound.
|
||||
struct NamedTransformFeedbackBinding {
|
||||
SharedPtr<BufferObject> Buffer;
|
||||
Range1D Range{};
|
||||
Bool HasExplicitRange = false;
|
||||
};
|
||||
|
||||
class GLContext {
|
||||
public:
|
||||
GLContext() = default;
|
||||
@@ -299,6 +307,17 @@ namespace MobileGL {
|
||||
// cannot express: an empty completed span is legal and draws nothing.
|
||||
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
|
||||
|
||||
// The by-name (direct state access) view. A named object that happens to be the
|
||||
// bound one is answered from the live copy, since that is where its state actually
|
||||
// is until a bind swaps it out.
|
||||
void CreateTransformFeedbackObject(Uint index);
|
||||
Bool IsNamedTransformFeedbackActive(Uint index) const;
|
||||
Bool IsNamedTransformFeedbackPaused(Uint index) const;
|
||||
NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const;
|
||||
void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||
Bool hasExplicitRange);
|
||||
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||
|
||||
Reference in New Issue
Block a user