Index: content/common/gpu/image_transport_surface_fbo_mac.cc |
diff --git a/content/common/gpu/image_transport_surface_mac.cc b/content/common/gpu/image_transport_surface_fbo_mac.cc |
similarity index 43% |
rename from content/common/gpu/image_transport_surface_mac.cc |
rename to content/common/gpu/image_transport_surface_fbo_mac.cc |
index 8341716613f83565d95b45c7a2fe2ab3f783cb1b..db70512e2eaad0136d6c43283a0079c9df0e668d 100644 |
--- a/content/common/gpu/image_transport_surface_mac.cc |
+++ b/content/common/gpu/image_transport_surface_fbo_mac.cc |
@@ -2,144 +2,29 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "content/common/gpu/image_transport_surface.h" |
+#include "content/common/gpu/image_transport_surface_fbo_mac.h" |
-#include "base/mac/scoped_cftyperef.h" |
-#include "base/memory/scoped_ptr.h" |
-#include "content/common/gpu/gpu_command_buffer_stub.h" |
#include "content/common/gpu/gpu_messages.h" |
+#include "content/common/gpu/image_transport_surface_iosurface_mac.h" |
#include "ui/gfx/native_widget_types.h" |
-#include "ui/gl/gl_bindings.h" |
#include "ui/gl/gl_context.h" |
#include "ui/gl/gl_implementation.h" |
-#include "ui/gl/gl_surface_cgl.h" |
#include "ui/gl/gl_surface_osmesa.h" |
-// Note that this must be included after gl_bindings.h to avoid conflicts. |
-#include <OpenGL/CGLIOSurface.h> |
- |
namespace content { |
-namespace { |
- |
-// IOSurface dimensions will be rounded up to a multiple of this value in order |
-// to reduce memory thrashing during resize. This must be a power of 2. |
-const uint32 kIOSurfaceDimensionRoundup = 64; |
- |
-int RoundUpSurfaceDimension(int number) { |
- DCHECK(number >= 0); |
- // Cast into unsigned space for portable bitwise ops. |
- uint32 unsigned_number = static_cast<uint32>(number); |
- uint32 roundup_sub_1 = kIOSurfaceDimensionRoundup - 1; |
- unsigned_number = (unsigned_number + roundup_sub_1) & ~roundup_sub_1; |
- return static_cast<int>(unsigned_number); |
-} |
- |
-// We are backed by an offscreen surface for the purposes of creating |
-// a context, but use FBOs to render to texture backed IOSurface |
-class IOSurfaceImageTransportSurface |
- : public gfx::NoOpGLSurfaceCGL, |
- public ImageTransportSurface, |
- public GpuCommandBufferStub::DestructionObserver { |
- public: |
- IOSurfaceImageTransportSurface(GpuChannelManager* manager, |
- GpuCommandBufferStub* stub, |
- gfx::PluginWindowHandle handle); |
- |
- // GLSurface implementation |
- virtual bool Initialize() OVERRIDE; |
- virtual void Destroy() OVERRIDE; |
- virtual bool DeferDraws() OVERRIDE; |
- virtual bool IsOffscreen() OVERRIDE; |
- virtual bool SwapBuffers() OVERRIDE; |
- virtual bool PostSubBuffer(int x, int y, int width, int height) OVERRIDE; |
- virtual bool SupportsPostSubBuffer() OVERRIDE; |
- virtual gfx::Size GetSize() OVERRIDE; |
- virtual bool OnMakeCurrent(gfx::GLContext* context) OVERRIDE; |
- virtual unsigned int GetBackingFrameBufferObject() OVERRIDE; |
- virtual bool SetBackbufferAllocation(bool allocated) OVERRIDE; |
- virtual void SetFrontbufferAllocation(bool allocated) OVERRIDE; |
- |
- protected: |
- // ImageTransportSurface implementation |
- virtual void OnBufferPresented( |
- const AcceleratedSurfaceMsg_BufferPresented_Params& params) OVERRIDE; |
- virtual void OnResize(gfx::Size size, float scale_factor) OVERRIDE; |
- virtual void SetLatencyInfo( |
- const std::vector<ui::LatencyInfo>&) OVERRIDE; |
- virtual void WakeUpGpu() OVERRIDE; |
- |
- // GpuCommandBufferStub::DestructionObserver implementation. |
- virtual void OnWillDestroyStub() OVERRIDE; |
- |
- private: |
- virtual ~IOSurfaceImageTransportSurface() OVERRIDE; |
- |
- void AdjustBufferAllocation(); |
- void UnrefIOSurface(); |
- void CreateIOSurface(); |
- |
- // Tracks the current buffer allocation state. |
- bool backbuffer_suggested_allocation_; |
- bool frontbuffer_suggested_allocation_; |
- |
- uint32 fbo_id_; |
- GLuint texture_id_; |
- GLuint depth_stencil_renderbuffer_id_; |
- |
- base::ScopedCFTypeRef<IOSurfaceRef> io_surface_; |
- |
- // The id of |io_surface_| or 0 if that's NULL. |
- uint64 io_surface_handle_; |
- |
- // Weak pointer to the context that this was last made current to. |
- gfx::GLContext* context_; |
- |
- gfx::Size size_; |
- gfx::Size rounded_size_; |
- float scale_factor_; |
- |
- // Whether or not we've successfully made the surface current once. |
- bool made_current_; |
- |
- // Whether a SwapBuffers is pending. |
- bool is_swap_buffers_pending_; |
- |
- // Whether we unscheduled command buffer because of pending SwapBuffers. |
- bool did_unschedule_; |
- |
- std::vector<ui::LatencyInfo> latency_info_; |
- |
- scoped_ptr<ImageTransportHelper> helper_; |
- |
- DISALLOW_COPY_AND_ASSIGN(IOSurfaceImageTransportSurface); |
-}; |
- |
-void AddBooleanValue(CFMutableDictionaryRef dictionary, |
- const CFStringRef key, |
- bool value) { |
- CFDictionaryAddValue(dictionary, key, |
- (value ? kCFBooleanTrue : kCFBooleanFalse)); |
-} |
- |
-void AddIntegerValue(CFMutableDictionaryRef dictionary, |
- const CFStringRef key, |
- int32 value) { |
- base::ScopedCFTypeRef<CFNumberRef> number( |
- CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); |
- CFDictionaryAddValue(dictionary, key, number.get()); |
-} |
-IOSurfaceImageTransportSurface::IOSurfaceImageTransportSurface( |
+ImageTransportSurfaceFBO::ImageTransportSurfaceFBO( |
+ StorageProvider* storage_provider, |
GpuChannelManager* manager, |
GpuCommandBufferStub* stub, |
gfx::PluginWindowHandle handle) |
- : gfx::NoOpGLSurfaceCGL(gfx::Size(1, 1)), |
+ : storage_provider_(storage_provider), |
backbuffer_suggested_allocation_(true), |
frontbuffer_suggested_allocation_(true), |
fbo_id_(0), |
texture_id_(0), |
depth_stencil_renderbuffer_id_(0), |
- io_surface_handle_(0), |
+ has_complete_framebuffer_(false), |
context_(NULL), |
scale_factor_(1.f), |
made_current_(false), |
@@ -148,10 +33,10 @@ IOSurfaceImageTransportSurface::IOSurfaceImageTransportSurface( |
helper_.reset(new ImageTransportHelper(this, manager, stub, handle)); |
} |
-IOSurfaceImageTransportSurface::~IOSurfaceImageTransportSurface() { |
+ImageTransportSurfaceFBO::~ImageTransportSurfaceFBO() { |
} |
-bool IOSurfaceImageTransportSurface::Initialize() { |
+bool ImageTransportSurfaceFBO::Initialize() { |
// Only support IOSurfaces if the GL implementation is the native desktop GL. |
// IO surfaces will not work with, for example, OSMesa software renderer |
// GL contexts. |
@@ -162,23 +47,17 @@ bool IOSurfaceImageTransportSurface::Initialize() { |
if (!helper_->Initialize()) |
return false; |
- if (!NoOpGLSurfaceCGL::Initialize()) { |
- helper_->Destroy(); |
- return false; |
- } |
- |
helper_->stub()->AddDestructionObserver(this); |
return true; |
} |
-void IOSurfaceImageTransportSurface::Destroy() { |
- UnrefIOSurface(); |
+void ImageTransportSurfaceFBO::Destroy() { |
+ DestroyFramebuffer(); |
helper_->Destroy(); |
- NoOpGLSurfaceCGL::Destroy(); |
} |
-bool IOSurfaceImageTransportSurface::DeferDraws() { |
+bool ImageTransportSurfaceFBO::DeferDraws() { |
// The command buffer hit a draw/clear command that could clobber the |
// IOSurface in use by an earlier SwapBuffers. If a Swap is pending, abort |
// processing of the command by returning true and unschedule until the Swap |
@@ -193,11 +72,11 @@ bool IOSurfaceImageTransportSurface::DeferDraws() { |
return false; |
} |
-bool IOSurfaceImageTransportSurface::IsOffscreen() { |
+bool ImageTransportSurfaceFBO::IsOffscreen() { |
return false; |
} |
-bool IOSurfaceImageTransportSurface::OnMakeCurrent(gfx::GLContext* context) { |
+bool ImageTransportSurfaceFBO::OnMakeCurrent(gfx::GLContext* context) { |
context_ = context; |
if (made_current_) |
@@ -209,11 +88,11 @@ bool IOSurfaceImageTransportSurface::OnMakeCurrent(gfx::GLContext* context) { |
return true; |
} |
-unsigned int IOSurfaceImageTransportSurface::GetBackingFrameBufferObject() { |
+unsigned int ImageTransportSurfaceFBO::GetBackingFrameBufferObject() { |
return fbo_id_; |
} |
-bool IOSurfaceImageTransportSurface::SetBackbufferAllocation(bool allocation) { |
+bool ImageTransportSurfaceFBO::SetBackbufferAllocation(bool allocation) { |
if (backbuffer_suggested_allocation_ == allocation) |
return true; |
backbuffer_suggested_allocation_ = allocation; |
@@ -221,34 +100,34 @@ bool IOSurfaceImageTransportSurface::SetBackbufferAllocation(bool allocation) { |
return true; |
} |
-void IOSurfaceImageTransportSurface::SetFrontbufferAllocation(bool allocation) { |
+void ImageTransportSurfaceFBO::SetFrontbufferAllocation(bool allocation) { |
if (frontbuffer_suggested_allocation_ == allocation) |
return; |
frontbuffer_suggested_allocation_ = allocation; |
AdjustBufferAllocation(); |
} |
-void IOSurfaceImageTransportSurface::AdjustBufferAllocation() { |
+void ImageTransportSurfaceFBO::AdjustBufferAllocation() { |
// On mac, the frontbuffer and backbuffer are the same buffer. The buffer is |
// free'd when both the browser and gpu processes have Unref'd the IOSurface. |
if (!backbuffer_suggested_allocation_ && |
!frontbuffer_suggested_allocation_ && |
- io_surface_.get()) { |
- UnrefIOSurface(); |
+ has_complete_framebuffer_) { |
+ DestroyFramebuffer(); |
helper_->Suspend(); |
- } else if (backbuffer_suggested_allocation_ && !io_surface_) { |
- CreateIOSurface(); |
+ } else if (backbuffer_suggested_allocation_ && !has_complete_framebuffer_) { |
+ CreateFramebuffer(); |
} |
} |
-bool IOSurfaceImageTransportSurface::SwapBuffers() { |
+bool ImageTransportSurfaceFBO::SwapBuffers() { |
DCHECK(backbuffer_suggested_allocation_); |
if (!frontbuffer_suggested_allocation_) |
return true; |
glFlush(); |
GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params; |
- params.surface_handle = io_surface_handle_; |
+ params.surface_handle = storage_provider_->GetSurfaceHandle(); |
params.size = GetSize(); |
params.scale_factor = scale_factor_; |
params.latency_info.swap(latency_info_); |
@@ -259,7 +138,7 @@ bool IOSurfaceImageTransportSurface::SwapBuffers() { |
return true; |
} |
-bool IOSurfaceImageTransportSurface::PostSubBuffer( |
+bool ImageTransportSurfaceFBO::PostSubBuffer( |
int x, int y, int width, int height) { |
DCHECK(backbuffer_suggested_allocation_); |
if (!frontbuffer_suggested_allocation_) |
@@ -267,7 +146,7 @@ bool IOSurfaceImageTransportSurface::PostSubBuffer( |
glFlush(); |
GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params params; |
- params.surface_handle = io_surface_handle_; |
+ params.surface_handle = storage_provider_->GetSurfaceHandle(); |
params.x = x; |
params.y = y; |
params.width = width; |
@@ -282,15 +161,23 @@ bool IOSurfaceImageTransportSurface::PostSubBuffer( |
return true; |
} |
-bool IOSurfaceImageTransportSurface::SupportsPostSubBuffer() { |
+bool ImageTransportSurfaceFBO::SupportsPostSubBuffer() { |
return true; |
} |
-gfx::Size IOSurfaceImageTransportSurface::GetSize() { |
+gfx::Size ImageTransportSurfaceFBO::GetSize() { |
return size_; |
} |
-void IOSurfaceImageTransportSurface::OnBufferPresented( |
+void* ImageTransportSurfaceFBO::GetHandle() { |
+ return NULL; |
+} |
+ |
+void* ImageTransportSurfaceFBO::GetDisplay() { |
+ return NULL; |
+} |
+ |
+void ImageTransportSurfaceFBO::OnBufferPresented( |
const AcceleratedSurfaceMsg_BufferPresented_Params& params) { |
DCHECK(is_swap_buffers_pending_); |
@@ -302,11 +189,11 @@ void IOSurfaceImageTransportSurface::OnBufferPresented( |
} |
} |
-void IOSurfaceImageTransportSurface::OnResize(gfx::Size size, |
- float scale_factor) { |
+void ImageTransportSurfaceFBO::OnResize(gfx::Size size, |
+ float scale_factor) { |
// This trace event is used in gpu_feature_browsertest.cc - the test will need |
// to be updated if this event is changed or moved. |
- TRACE_EVENT2("gpu", "IOSurfaceImageTransportSurface::OnResize", |
+ TRACE_EVENT2("gpu", "ImageTransportSurfaceFBO::OnResize", |
"old_width", size_.width(), "new_width", size.width()); |
// Caching |context_| from OnMakeCurrent. It should still be current. |
DCHECK(context_->IsCurrent(this)); |
@@ -314,25 +201,25 @@ void IOSurfaceImageTransportSurface::OnResize(gfx::Size size, |
size_ = size; |
scale_factor_ = scale_factor; |
- CreateIOSurface(); |
+ CreateFramebuffer(); |
} |
-void IOSurfaceImageTransportSurface::SetLatencyInfo( |
+void ImageTransportSurfaceFBO::SetLatencyInfo( |
const std::vector<ui::LatencyInfo>& latency_info) { |
for (size_t i = 0; i < latency_info.size(); i++) |
latency_info_.push_back(latency_info[i]); |
} |
-void IOSurfaceImageTransportSurface::WakeUpGpu() { |
+void ImageTransportSurfaceFBO::WakeUpGpu() { |
NOTIMPLEMENTED(); |
} |
-void IOSurfaceImageTransportSurface::OnWillDestroyStub() { |
+void ImageTransportSurfaceFBO::OnWillDestroyStub() { |
helper_->stub()->RemoveDestructionObserver(this); |
Destroy(); |
} |
-void IOSurfaceImageTransportSurface::UnrefIOSurface() { |
+void ImageTransportSurfaceFBO::DestroyFramebuffer() { |
// If we have resources to destroy, then make sure that we have a current |
// context which we can use to delete the resources. |
if (context_ || fbo_id_ || texture_id_ || depth_stencil_renderbuffer_id_) { |
@@ -356,53 +243,53 @@ void IOSurfaceImageTransportSurface::UnrefIOSurface() { |
depth_stencil_renderbuffer_id_ = 0; |
} |
- io_surface_.reset(); |
- io_surface_handle_ = 0; |
+ storage_provider_->FreeColorBufferStorage(); |
+ |
+ has_complete_framebuffer_ = false; |
} |
-void IOSurfaceImageTransportSurface::CreateIOSurface() { |
- gfx::Size new_rounded_size(RoundUpSurfaceDimension(size_.width()), |
- RoundUpSurfaceDimension(size_.height())); |
+void ImageTransportSurfaceFBO::CreateFramebuffer() { |
+ gfx::Size new_rounded_size = storage_provider_->GetRoundedSize(size_); |
// Only recreate surface when the rounded up size has changed. |
- if (io_surface_.get() && new_rounded_size == rounded_size_) |
+ if (has_complete_framebuffer_ && new_rounded_size == rounded_size_) |
return; |
// This trace event is used in gpu_feature_browsertest.cc - the test will need |
// to be updated if this event is changed or moved. |
- TRACE_EVENT2("gpu", "IOSurfaceImageTransportSurface::CreateIOSurface", |
+ TRACE_EVENT2("gpu", "ImageTransportSurfaceFBO::CreateFramebuffer", |
"width", new_rounded_size.width(), |
"height", new_rounded_size.height()); |
rounded_size_ = new_rounded_size; |
+ // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on |
+ // Mac OS X and is required for IOSurface interoperability. |
GLint previous_texture_id = 0; |
glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE_ARB, &previous_texture_id); |
// Free the old IO Surface first to reduce memory fragmentation. |
- UnrefIOSurface(); |
+ DestroyFramebuffer(); |
glGenFramebuffersEXT(1, &fbo_id_); |
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_id_); |
glGenTextures(1, &texture_id_); |
- // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on |
- // Mac OS X and is required for IOSurface interoperability. |
- GLenum target = GL_TEXTURE_RECTANGLE_ARB; |
- glBindTexture(target, texture_id_); |
- glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
- glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
- glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
- glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id_); |
+ glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
+ glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
+ glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, |
+ GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
+ glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, |
+ GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, |
GL_COLOR_ATTACHMENT0_EXT, |
- target, |
+ GL_TEXTURE_RECTANGLE_ARB, |
texture_id_, |
0); |
- |
// Search through the provided attributes; if the caller has |
// requested a stencil buffer, try to get one. |
@@ -433,116 +320,26 @@ void IOSurfaceImageTransportSurface::CreateIOSurface() { |
// do stencil-based operations. |
} |
- // Allocate a new IOSurface, which is the GPU resource that can be |
- // shared across processes. |
- base::ScopedCFTypeRef<CFMutableDictionaryRef> properties; |
- properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, |
- 0, |
- &kCFTypeDictionaryKeyCallBacks, |
- &kCFTypeDictionaryValueCallBacks)); |
- AddIntegerValue(properties, |
- kIOSurfaceWidth, |
- rounded_size_.width()); |
- AddIntegerValue(properties, |
- kIOSurfaceHeight, |
- rounded_size_.height()); |
- AddIntegerValue(properties, |
- kIOSurfaceBytesPerElement, 4); |
- AddBooleanValue(properties, |
- kIOSurfaceIsGlobal, true); |
- // I believe we should be able to unreference the IOSurfaces without |
- // synchronizing with the browser process because they are |
- // ultimately reference counted by the operating system. |
- io_surface_.reset(IOSurfaceCreate(properties)); |
- io_surface_handle_ = IOSurfaceGetID(io_surface_); |
- |
- // Don't think we need to identify a plane. |
- GLuint plane = 0; |
- CGLError cglerror = |
- CGLTexImageIOSurface2D( |
- static_cast<CGLContextObj>(context_->GetHandle()), |
- target, |
- GL_RGBA, |
- rounded_size_.width(), |
- rounded_size_.height(), |
- GL_BGRA, |
- GL_UNSIGNED_INT_8_8_8_8_REV, |
- io_surface_.get(), |
- plane); |
- if (cglerror != kCGLNoError) { |
- UnrefIOSurface(); |
+ bool allocated_color_buffer = storage_provider_->AllocateColorBufferStorage( |
+ static_cast<CGLContextObj>(context_->GetHandle()), |
+ rounded_size_); |
+ if (!allocated_color_buffer) { |
+ DLOG(ERROR) << "Failed to allocate color buffer storage."; |
+ DestroyFramebuffer(); |
return; |
} |
- glFlush(); |
- |
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); |
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { |
DLOG(ERROR) << "Framebuffer was incomplete: " << status; |
- UnrefIOSurface(); |
+ DestroyFramebuffer(); |
return; |
} |
- glBindTexture(target, previous_texture_id); |
- // The FBO remains bound for this GL context. |
-} |
- |
-// A subclass of GLSurfaceOSMesa that doesn't print an error message when |
-// SwapBuffers() is called. |
-class DRTSurfaceOSMesa : public gfx::GLSurfaceOSMesa { |
- public: |
- // Size doesn't matter, the surface is resized to the right size later. |
- DRTSurfaceOSMesa() : GLSurfaceOSMesa(GL_RGBA, gfx::Size(1, 1)) {} |
- |
- // Implement a subset of GLSurface. |
- virtual bool SwapBuffers() OVERRIDE; |
- |
- private: |
- virtual ~DRTSurfaceOSMesa() {} |
- DISALLOW_COPY_AND_ASSIGN(DRTSurfaceOSMesa); |
-}; |
- |
-bool DRTSurfaceOSMesa::SwapBuffers() { |
- return true; |
-} |
- |
-bool g_allow_os_mesa = false; |
- |
-} // namespace |
+ has_complete_framebuffer_ = true; |
-// static |
-scoped_refptr<gfx::GLSurface> ImageTransportSurface::CreateNativeSurface( |
- GpuChannelManager* manager, |
- GpuCommandBufferStub* stub, |
- const gfx::GLSurfaceHandle& surface_handle) { |
- DCHECK(surface_handle.transport_type == gfx::NATIVE_DIRECT || |
- surface_handle.transport_type == gfx::NATIVE_TRANSPORT); |
- |
- switch (gfx::GetGLImplementation()) { |
- case gfx::kGLImplementationDesktopGL: |
- case gfx::kGLImplementationAppleGL: |
- return scoped_refptr<gfx::GLSurface>(new IOSurfaceImageTransportSurface( |
- manager, stub, surface_handle.handle)); |
- |
- default: |
- // Content shell in DRT mode spins up a gpu process which needs an |
- // image transport surface, but that surface isn't used to read pixel |
- // baselines. So this is mostly a dummy surface. |
- if (!g_allow_os_mesa) { |
- NOTREACHED(); |
- return scoped_refptr<gfx::GLSurface>(); |
- } |
- scoped_refptr<gfx::GLSurface> surface(new DRTSurfaceOSMesa()); |
- if (!surface.get() || !surface->Initialize()) |
- return surface; |
- return scoped_refptr<gfx::GLSurface>(new PassThroughImageTransportSurface( |
- manager, stub, surface.get())); |
- } |
-} |
- |
-// static |
-void ImageTransportSurface::SetAllowOSMesaForTesting(bool allow) { |
- g_allow_os_mesa = allow; |
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, previous_texture_id); |
+ // The FBO remains bound for this GL context. |
} |
} // namespace content |