1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "gpu/command_buffer/service/framebuffer_manager.h"
6 #include "base/logging.h"
7 #include "base/strings/stringprintf.h"
8 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
9 #include "gpu/command_buffer/service/renderbuffer_manager.h"
10 #include "gpu/command_buffer/service/texture_manager.h"
11 #include "ui/gl/gl_bindings.h"
16 DecoderFramebufferState::DecoderFramebufferState()
17 : clear_state_dirty(false),
18 bound_read_framebuffer(NULL
),
19 bound_draw_framebuffer(NULL
) {
22 DecoderFramebufferState::~DecoderFramebufferState() {
25 Framebuffer::FramebufferComboCompleteMap
*
26 Framebuffer::framebuffer_combo_complete_map_
;
28 // Framebuffer completeness is not cacheable on OS X because of dynamic
29 // graphics switching.
30 // http://crbug.com/180876
31 #if defined(OS_MACOSX)
32 bool Framebuffer::allow_framebuffer_combo_complete_map_
= false;
34 bool Framebuffer::allow_framebuffer_combo_complete_map_
= true;
37 void Framebuffer::ClearFramebufferCompleteComboMap() {
38 if (framebuffer_combo_complete_map_
) {
39 framebuffer_combo_complete_map_
->clear();
43 class RenderbufferAttachment
44 : public Framebuffer::Attachment
{
46 explicit RenderbufferAttachment(
47 Renderbuffer
* renderbuffer
)
48 : renderbuffer_(renderbuffer
) {
51 GLsizei
width() const override
{ return renderbuffer_
->width(); }
53 GLsizei
height() const override
{ return renderbuffer_
->height(); }
55 GLenum
internal_format() const override
{
56 return renderbuffer_
->internal_format();
59 GLenum
texture_type() const override
{ return 0; }
61 GLsizei
samples() const override
{ return renderbuffer_
->samples(); }
63 GLuint
object_name() const override
{ return renderbuffer_
->client_id(); }
65 bool cleared() const override
{ return renderbuffer_
->cleared(); }
67 void SetCleared(RenderbufferManager
* renderbuffer_manager
,
68 TextureManager
* /* texture_manager */,
69 bool cleared
) override
{
70 renderbuffer_manager
->SetCleared(renderbuffer_
.get(), cleared
);
73 bool IsTexture(TextureRef
* /* texture */) const override
{ return false; }
75 bool IsRenderbuffer(Renderbuffer
* renderbuffer
) const override
{
76 return renderbuffer_
.get() == renderbuffer
;
79 bool CanRenderTo() const override
{ return true; }
81 void DetachFromFramebuffer(Framebuffer
* framebuffer
) const override
{
82 // Nothing to do for renderbuffers.
85 bool ValidForAttachmentType(GLenum attachment_type
,
86 uint32 max_color_attachments
) override
{
87 uint32 need
= GLES2Util::GetChannelsNeededForAttachmentType(
88 attachment_type
, max_color_attachments
);
89 uint32 have
= GLES2Util::GetChannelsForFormat(internal_format());
90 return (need
& have
) != 0;
93 Renderbuffer
* renderbuffer() const {
94 return renderbuffer_
.get();
97 size_t GetSignatureSize(TextureManager
* texture_manager
) const override
{
98 return renderbuffer_
->GetSignatureSize();
101 void AddToSignature(TextureManager
* texture_manager
,
102 std::string
* signature
) const override
{
104 renderbuffer_
->AddToSignature(signature
);
107 void OnWillRenderTo() const override
{}
108 void OnDidRenderTo() const override
{}
109 bool FormsFeedbackLoop(TextureRef
* /* texture */,
110 GLint
/*level */) const override
{
115 ~RenderbufferAttachment() override
{}
118 scoped_refptr
<Renderbuffer
> renderbuffer_
;
120 DISALLOW_COPY_AND_ASSIGN(RenderbufferAttachment
);
123 class TextureAttachment
124 : public Framebuffer::Attachment
{
127 TextureRef
* texture_ref
, GLenum target
, GLint level
, GLsizei samples
)
128 : texture_ref_(texture_ref
),
134 GLsizei
width() const override
{
135 GLsizei temp_width
= 0;
136 GLsizei temp_height
= 0;
137 texture_ref_
->texture()->GetLevelSize(
138 target_
, level_
, &temp_width
, &temp_height
, nullptr);
142 GLsizei
height() const override
{
143 GLsizei temp_width
= 0;
144 GLsizei temp_height
= 0;
145 texture_ref_
->texture()->GetLevelSize(
146 target_
, level_
, &temp_width
, &temp_height
, nullptr);
150 GLenum
internal_format() const override
{
151 GLenum temp_type
= 0;
152 GLenum temp_internal_format
= 0;
153 texture_ref_
->texture()->GetLevelType(
154 target_
, level_
, &temp_type
, &temp_internal_format
);
155 return temp_internal_format
;
158 GLenum
texture_type() const override
{
159 GLenum temp_type
= 0;
160 GLenum temp_internal_format
= 0;
161 texture_ref_
->texture()->GetLevelType(
162 target_
, level_
, &temp_type
, &temp_internal_format
);
166 GLsizei
samples() const override
{ return samples_
; }
168 GLuint
object_name() const override
{ return texture_ref_
->client_id(); }
170 bool cleared() const override
{
171 return texture_ref_
->texture()->IsLevelCleared(target_
, level_
);
174 void SetCleared(RenderbufferManager
* /* renderbuffer_manager */,
175 TextureManager
* texture_manager
,
176 bool cleared
) override
{
177 texture_manager
->SetLevelCleared(
178 texture_ref_
.get(), target_
, level_
, cleared
);
181 bool IsTexture(TextureRef
* texture
) const override
{
182 return texture
== texture_ref_
.get();
185 bool IsRenderbuffer(Renderbuffer
* /* renderbuffer */) const override
{
189 TextureRef
* texture() const {
190 return texture_ref_
.get();
193 bool CanRenderTo() const override
{
194 return texture_ref_
->texture()->CanRenderTo();
197 void DetachFromFramebuffer(Framebuffer
* framebuffer
) const override
{
198 texture_ref_
->texture()->DetachFromFramebuffer();
199 framebuffer
->OnTextureRefDetached(texture_ref_
.get());
202 bool ValidForAttachmentType(GLenum attachment_type
,
203 uint32 max_color_attachments
) override
{
205 GLenum internal_format
= 0;
206 if (!texture_ref_
->texture()->GetLevelType(
207 target_
, level_
, &type
, &internal_format
)) {
210 uint32 need
= GLES2Util::GetChannelsNeededForAttachmentType(
211 attachment_type
, max_color_attachments
);
212 uint32 have
= GLES2Util::GetChannelsForFormat(internal_format
);
214 // Workaround for NVIDIA drivers that incorrectly expose these formats as
216 if (internal_format
== GL_LUMINANCE
|| internal_format
== GL_ALPHA
||
217 internal_format
== GL_LUMINANCE_ALPHA
) {
220 return (need
& have
) != 0;
223 size_t GetSignatureSize(TextureManager
* texture_manager
) const override
{
224 return texture_manager
->GetSignatureSize();
227 void AddToSignature(TextureManager
* texture_manager
,
228 std::string
* signature
) const override
{
230 texture_manager
->AddToSignature(
231 texture_ref_
.get(), target_
, level_
, signature
);
234 void OnWillRenderTo() const override
{
235 texture_ref_
->texture()->OnWillModifyPixels();
238 void OnDidRenderTo() const override
{
239 texture_ref_
->texture()->OnDidModifyPixels();
242 bool FormsFeedbackLoop(TextureRef
* texture
, GLint level
) const override
{
243 return texture
== texture_ref_
.get() && level
== level_
;
247 ~TextureAttachment() override
{}
250 scoped_refptr
<TextureRef
> texture_ref_
;
255 DISALLOW_COPY_AND_ASSIGN(TextureAttachment
);
258 FramebufferManager::TextureDetachObserver::TextureDetachObserver() {}
260 FramebufferManager::TextureDetachObserver::~TextureDetachObserver() {}
262 FramebufferManager::FramebufferManager(
263 uint32 max_draw_buffers
, uint32 max_color_attachments
,
264 ContextGroup::ContextType context_type
)
265 : framebuffer_state_change_count_(1),
266 framebuffer_count_(0),
268 max_draw_buffers_(max_draw_buffers
),
269 max_color_attachments_(max_color_attachments
),
270 context_type_(context_type
) {
271 DCHECK_GT(max_draw_buffers_
, 0u);
272 DCHECK_GT(max_color_attachments_
, 0u);
275 FramebufferManager::~FramebufferManager() {
276 DCHECK(framebuffers_
.empty());
277 // If this triggers, that means something is keeping a reference to a
278 // Framebuffer belonging to this.
279 CHECK_EQ(framebuffer_count_
, 0u);
282 void Framebuffer::MarkAsDeleted() {
284 while (!attachments_
.empty()) {
285 Attachment
* attachment
= attachments_
.begin()->second
.get();
286 attachment
->DetachFromFramebuffer(this);
287 attachments_
.erase(attachments_
.begin());
291 void FramebufferManager::Destroy(bool have_context
) {
292 have_context_
= have_context
;
293 framebuffers_
.clear();
296 void FramebufferManager::StartTracking(
297 Framebuffer
* /* framebuffer */) {
298 ++framebuffer_count_
;
301 void FramebufferManager::StopTracking(
302 Framebuffer
* /* framebuffer */) {
303 --framebuffer_count_
;
306 void FramebufferManager::CreateFramebuffer(
307 GLuint client_id
, GLuint service_id
) {
308 std::pair
<FramebufferMap::iterator
, bool> result
=
309 framebuffers_
.insert(
312 scoped_refptr
<Framebuffer
>(
313 new Framebuffer(this, service_id
))));
314 DCHECK(result
.second
);
317 Framebuffer::Framebuffer(
318 FramebufferManager
* manager
, GLuint service_id
)
321 service_id_(service_id
),
322 has_been_bound_(false),
323 framebuffer_complete_state_count_id_(0) {
324 manager
->StartTracking(this);
325 DCHECK_GT(manager
->max_draw_buffers_
, 0u);
326 draw_buffers_
.reset(new GLenum
[manager
->max_draw_buffers_
]);
327 draw_buffers_
[0] = GL_COLOR_ATTACHMENT0
;
328 for (uint32 i
= 1; i
< manager
->max_draw_buffers_
; ++i
)
329 draw_buffers_
[i
] = GL_NONE
;
332 Framebuffer::~Framebuffer() {
334 if (manager_
->have_context_
) {
335 GLuint id
= service_id();
336 glDeleteFramebuffersEXT(1, &id
);
338 manager_
->StopTracking(this);
343 bool Framebuffer::HasUnclearedAttachment(
344 GLenum attachment
) const {
345 AttachmentMap::const_iterator it
=
346 attachments_
.find(attachment
);
347 if (it
!= attachments_
.end()) {
348 const Attachment
* attachment
= it
->second
.get();
349 return !attachment
->cleared();
354 bool Framebuffer::HasUnclearedColorAttachments() const {
355 for (AttachmentMap::const_iterator it
= attachments_
.begin();
356 it
!= attachments_
.end(); ++it
) {
357 if (it
->first
>= GL_COLOR_ATTACHMENT0
&&
358 it
->first
< GL_COLOR_ATTACHMENT0
+ manager_
->max_draw_buffers_
) {
359 const Attachment
* attachment
= it
->second
.get();
360 if (!attachment
->cleared())
367 void Framebuffer::ChangeDrawBuffersHelper(bool recover
) const {
368 scoped_ptr
<GLenum
[]> buffers(new GLenum
[manager_
->max_draw_buffers_
]);
369 for (uint32 i
= 0; i
< manager_
->max_draw_buffers_
; ++i
)
370 buffers
[i
] = GL_NONE
;
371 for (AttachmentMap::const_iterator it
= attachments_
.begin();
372 it
!= attachments_
.end(); ++it
) {
373 if (it
->first
>= GL_COLOR_ATTACHMENT0
&&
374 it
->first
< GL_COLOR_ATTACHMENT0
+ manager_
->max_draw_buffers_
) {
375 buffers
[it
->first
- GL_COLOR_ATTACHMENT0
] = it
->first
;
378 bool different
= false;
379 for (uint32 i
= 0; i
< manager_
->max_draw_buffers_
; ++i
) {
380 if (buffers
[i
] != draw_buffers_
[i
]) {
387 glDrawBuffersARB(manager_
->max_draw_buffers_
, draw_buffers_
.get());
389 glDrawBuffersARB(manager_
->max_draw_buffers_
, buffers
.get());
393 void Framebuffer::PrepareDrawBuffersForClear() const {
394 bool recover
= false;
395 ChangeDrawBuffersHelper(recover
);
398 void Framebuffer::RestoreDrawBuffersAfterClear() const {
400 ChangeDrawBuffersHelper(recover
);
403 void Framebuffer::MarkAttachmentAsCleared(
404 RenderbufferManager
* renderbuffer_manager
,
405 TextureManager
* texture_manager
,
408 AttachmentMap::iterator it
= attachments_
.find(attachment
);
409 if (it
!= attachments_
.end()) {
410 Attachment
* a
= it
->second
.get();
411 if (a
->cleared() != cleared
) {
412 a
->SetCleared(renderbuffer_manager
,
419 void Framebuffer::MarkAttachmentsAsCleared(
420 RenderbufferManager
* renderbuffer_manager
,
421 TextureManager
* texture_manager
,
423 for (AttachmentMap::iterator it
= attachments_
.begin();
424 it
!= attachments_
.end(); ++it
) {
425 Attachment
* attachment
= it
->second
.get();
426 if (attachment
->cleared() != cleared
) {
427 attachment
->SetCleared(renderbuffer_manager
, texture_manager
, cleared
);
432 bool Framebuffer::HasDepthAttachment() const {
433 return attachments_
.find(GL_DEPTH_STENCIL_ATTACHMENT
) != attachments_
.end() ||
434 attachments_
.find(GL_DEPTH_ATTACHMENT
) != attachments_
.end();
437 bool Framebuffer::HasStencilAttachment() const {
438 return attachments_
.find(GL_DEPTH_STENCIL_ATTACHMENT
) != attachments_
.end() ||
439 attachments_
.find(GL_STENCIL_ATTACHMENT
) != attachments_
.end();
442 GLenum
Framebuffer::GetColorAttachmentFormat() const {
443 AttachmentMap::const_iterator it
= attachments_
.find(GL_COLOR_ATTACHMENT0
);
444 if (it
== attachments_
.end()) {
447 const Attachment
* attachment
= it
->second
.get();
448 return attachment
->internal_format();
451 GLenum
Framebuffer::GetColorAttachmentTextureType() const {
452 AttachmentMap::const_iterator it
= attachments_
.find(GL_COLOR_ATTACHMENT0
);
453 if (it
== attachments_
.end()) {
456 const Attachment
* attachment
= it
->second
.get();
457 return attachment
->texture_type();
460 GLenum
Framebuffer::IsPossiblyComplete() const {
461 if (attachments_
.empty()) {
462 return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
;
467 for (AttachmentMap::const_iterator it
= attachments_
.begin();
468 it
!= attachments_
.end(); ++it
) {
469 GLenum attachment_type
= it
->first
;
470 Attachment
* attachment
= it
->second
.get();
471 if (!attachment
->ValidForAttachmentType(attachment_type
,
472 manager_
->max_color_attachments_
)) {
473 return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
;
476 width
= attachment
->width();
477 height
= attachment
->height();
478 if (width
== 0 || height
== 0) {
479 return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
;
481 } else if (manager_
->context_type() != ContextGroup::CONTEXT_TYPE_WEBGL2
) {
482 // TODO(zmo): revisit this if we create ES3 contexts for clients other
484 if (attachment
->width() != width
|| attachment
->height() != height
) {
485 return GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT
;
489 if (!attachment
->CanRenderTo()) {
490 return GL_FRAMEBUFFER_UNSUPPORTED
;
494 // This does not mean the framebuffer is actually complete. It just means our
496 return GL_FRAMEBUFFER_COMPLETE
;
499 GLenum
Framebuffer::GetStatus(
500 TextureManager
* texture_manager
, GLenum target
) const {
501 // Check if we have this combo already.
502 std::string signature
;
503 if (allow_framebuffer_combo_complete_map_
) {
504 size_t signature_size
= sizeof(target
);
505 for (AttachmentMap::const_iterator it
= attachments_
.begin();
506 it
!= attachments_
.end(); ++it
) {
507 Attachment
* attachment
= it
->second
.get();
508 signature_size
+= sizeof(it
->first
) +
509 attachment
->GetSignatureSize(texture_manager
);
512 signature
.reserve(signature_size
);
513 signature
.append(reinterpret_cast<const char*>(&target
), sizeof(target
));
515 for (AttachmentMap::const_iterator it
= attachments_
.begin();
516 it
!= attachments_
.end(); ++it
) {
517 Attachment
* attachment
= it
->second
.get();
518 signature
.append(reinterpret_cast<const char*>(&it
->first
),
520 attachment
->AddToSignature(texture_manager
, &signature
);
522 DCHECK(signature
.size() == signature_size
);
524 if (!framebuffer_combo_complete_map_
) {
525 framebuffer_combo_complete_map_
= new FramebufferComboCompleteMap();
528 FramebufferComboCompleteMap::const_iterator it
=
529 framebuffer_combo_complete_map_
->find(signature
);
530 if (it
!= framebuffer_combo_complete_map_
->end()) {
531 return GL_FRAMEBUFFER_COMPLETE
;
535 GLenum result
= glCheckFramebufferStatusEXT(target
);
537 // Insert the new result into the combo map.
538 if (allow_framebuffer_combo_complete_map_
&&
539 result
== GL_FRAMEBUFFER_COMPLETE
) {
540 framebuffer_combo_complete_map_
->insert(std::make_pair(signature
, true));
546 bool Framebuffer::IsCleared() const {
547 // are all the attachments cleaared?
548 for (AttachmentMap::const_iterator it
= attachments_
.begin();
549 it
!= attachments_
.end(); ++it
) {
550 Attachment
* attachment
= it
->second
.get();
551 if (!attachment
->cleared()) {
558 GLenum
Framebuffer::GetDrawBuffer(GLenum draw_buffer
) const {
559 GLsizei index
= static_cast<GLsizei
>(
560 draw_buffer
- GL_DRAW_BUFFER0_ARB
);
562 index
< static_cast<GLsizei
>(manager_
->max_draw_buffers_
));
563 return draw_buffers_
[index
];
566 void Framebuffer::SetDrawBuffers(GLsizei n
, const GLenum
* bufs
) {
567 DCHECK(n
<= static_cast<GLsizei
>(manager_
->max_draw_buffers_
));
568 for (GLsizei i
= 0; i
< n
; ++i
)
569 draw_buffers_
[i
] = bufs
[i
];
572 bool Framebuffer::HasAlphaMRT() const {
573 for (uint32 i
= 0; i
< manager_
->max_draw_buffers_
; ++i
) {
574 if (draw_buffers_
[i
] != GL_NONE
) {
575 const Attachment
* attachment
= GetAttachment(draw_buffers_
[i
]);
578 if ((GLES2Util::GetChannelsForFormat(
579 attachment
->internal_format()) & 0x0008) != 0)
586 void Framebuffer::UnbindRenderbuffer(
587 GLenum target
, Renderbuffer
* renderbuffer
) {
591 for (AttachmentMap::const_iterator it
= attachments_
.begin();
592 it
!= attachments_
.end(); ++it
) {
593 Attachment
* attachment
= it
->second
.get();
594 if (attachment
->IsRenderbuffer(renderbuffer
)) {
595 // TODO(gman): manually detach renderbuffer.
596 // glFramebufferRenderbufferEXT(target, it->first, GL_RENDERBUFFER, 0);
597 AttachRenderbuffer(it
->first
, NULL
);
605 void Framebuffer::UnbindTexture(
606 GLenum target
, TextureRef
* texture_ref
) {
610 for (AttachmentMap::const_iterator it
= attachments_
.begin();
611 it
!= attachments_
.end(); ++it
) {
612 Attachment
* attachment
= it
->second
.get();
613 if (attachment
->IsTexture(texture_ref
)) {
614 // TODO(gman): manually detach texture.
615 // glFramebufferTexture2DEXT(target, it->first, GL_TEXTURE_2D, 0, 0);
616 AttachTexture(it
->first
, NULL
, GL_TEXTURE_2D
, 0, 0);
624 Framebuffer
* FramebufferManager::GetFramebuffer(
626 FramebufferMap::iterator it
= framebuffers_
.find(client_id
);
627 return it
!= framebuffers_
.end() ? it
->second
.get() : NULL
;
630 void FramebufferManager::RemoveFramebuffer(GLuint client_id
) {
631 FramebufferMap::iterator it
= framebuffers_
.find(client_id
);
632 if (it
!= framebuffers_
.end()) {
633 it
->second
->MarkAsDeleted();
634 framebuffers_
.erase(it
);
638 void Framebuffer::DoUnbindGLAttachmentsForWorkaround(GLenum target
) {
639 // Replace all attachments with the default Renderbuffer.
640 for (AttachmentMap::const_iterator it
= attachments_
.begin();
641 it
!= attachments_
.end(); ++it
) {
642 glFramebufferRenderbufferEXT(target
, it
->first
, GL_RENDERBUFFER
, 0);
646 void Framebuffer::AttachRenderbuffer(
647 GLenum attachment
, Renderbuffer
* renderbuffer
) {
648 const Attachment
* a
= GetAttachment(attachment
);
650 a
->DetachFromFramebuffer(this);
652 attachments_
[attachment
] = scoped_refptr
<Attachment
>(
653 new RenderbufferAttachment(renderbuffer
));
655 attachments_
.erase(attachment
);
657 framebuffer_complete_state_count_id_
= 0;
660 void Framebuffer::AttachTexture(
661 GLenum attachment
, TextureRef
* texture_ref
, GLenum target
,
662 GLint level
, GLsizei samples
) {
663 const Attachment
* a
= GetAttachment(attachment
);
665 a
->DetachFromFramebuffer(this);
667 attachments_
[attachment
] = scoped_refptr
<Attachment
>(
668 new TextureAttachment(texture_ref
, target
, level
, samples
));
669 texture_ref
->texture()->AttachToFramebuffer();
671 attachments_
.erase(attachment
);
673 framebuffer_complete_state_count_id_
= 0;
676 const Framebuffer::Attachment
*
677 Framebuffer::GetAttachment(
678 GLenum attachment
) const {
679 AttachmentMap::const_iterator it
= attachments_
.find(attachment
);
680 if (it
!= attachments_
.end()) {
681 return it
->second
.get();
686 void Framebuffer::OnTextureRefDetached(TextureRef
* texture
) {
687 manager_
->OnTextureRefDetached(texture
);
690 void Framebuffer::OnWillRenderTo() const {
691 for (AttachmentMap::const_iterator it
= attachments_
.begin();
692 it
!= attachments_
.end(); ++it
) {
693 it
->second
->OnWillRenderTo();
697 void Framebuffer::OnDidRenderTo() const {
698 for (AttachmentMap::const_iterator it
= attachments_
.begin();
699 it
!= attachments_
.end(); ++it
) {
700 it
->second
->OnDidRenderTo();
704 bool FramebufferManager::GetClientId(
705 GLuint service_id
, GLuint
* client_id
) const {
706 // This doesn't need to be fast. It's only used during slow queries.
707 for (FramebufferMap::const_iterator it
= framebuffers_
.begin();
708 it
!= framebuffers_
.end(); ++it
) {
709 if (it
->second
->service_id() == service_id
) {
710 *client_id
= it
->first
;
717 void FramebufferManager::MarkAttachmentsAsCleared(
718 Framebuffer
* framebuffer
,
719 RenderbufferManager
* renderbuffer_manager
,
720 TextureManager
* texture_manager
) {
722 framebuffer
->MarkAttachmentsAsCleared(renderbuffer_manager
,
725 MarkAsComplete(framebuffer
);
728 void FramebufferManager::MarkAsComplete(
729 Framebuffer
* framebuffer
) {
731 framebuffer
->MarkAsComplete(framebuffer_state_change_count_
);
734 bool FramebufferManager::IsComplete(
735 Framebuffer
* framebuffer
) {
737 return framebuffer
->framebuffer_complete_state_count_id() ==
738 framebuffer_state_change_count_
;
741 void FramebufferManager::OnTextureRefDetached(TextureRef
* texture
) {
742 for (TextureDetachObserverVector::iterator it
=
743 texture_detach_observers_
.begin();
744 it
!= texture_detach_observers_
.end();
746 TextureDetachObserver
* observer
= *it
;
747 observer
->OnTextureRefDetachedFromFramebuffer(texture
);