3980d09f8ae3b22dfd2bdd06b2fc0653acfdce15
[mesa.git] / src / mesa / main / fbobject.c
1 /*
2 * Mesa 3-D graphics library
3 * Version: 6.3
4 *
5 * Copyright (C) 1999-2005 Brian Paul All Rights Reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
21 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25
26 /*
27 * Authors:
28 * Brian Paul
29 */
30
31
32 #include "context.h"
33 #include "fbobject.h"
34 #include "framebuffer.h"
35 #include "hash.h"
36 #include "renderbuffer.h"
37 #include "teximage.h"
38 #include "texstore.h"
39
40
41 /**
42 * Notes:
43 *
44 * None of the GL_EXT_framebuffer_object functions are compiled into
45 * display lists.
46 */
47
48
49
50 /*
51 * When glGenRender/FramebuffersEXT() is called we insert pointers to
52 * these placeholder objects into the hash table.
53 * Later, when the object ID is first bound, we replace the placeholder
54 * with the real frame/renderbuffer.
55 */
56 static struct gl_framebuffer DummyFramebuffer;
57 static struct gl_renderbuffer DummyRenderbuffer;
58
59
60 #define IS_CUBE_FACE(TARGET) \
61 ((TARGET) >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && \
62 (TARGET) <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)
63
64
65 /**
66 * Helper routine for getting a gl_renderbuffer.
67 */
68 static struct gl_renderbuffer *
69 lookup_renderbuffer(GLcontext *ctx, GLuint id)
70 {
71 struct gl_renderbuffer *rb;
72
73 if (id == 0)
74 return NULL;
75
76 rb = (struct gl_renderbuffer *)
77 _mesa_HashLookup(ctx->Shared->RenderBuffers, id);
78 return rb;
79 }
80
81
82 /**
83 * Helper routine for getting a gl_framebuffer.
84 */
85 static struct gl_framebuffer *
86 lookup_framebuffer(GLcontext *ctx, GLuint id)
87 {
88 struct gl_framebuffer *fb;
89
90 if (id == 0)
91 return NULL;
92
93 fb = (struct gl_framebuffer *)
94 _mesa_HashLookup(ctx->Shared->FrameBuffers, id);
95 return fb;
96 }
97
98
99 /**
100 * Given a GL_*_ATTACHMENTn token, return a pointer to the corresponding
101 * gl_renderbuffer_attachment object.
102 */
103 static struct gl_renderbuffer_attachment *
104 get_attachment(GLcontext *ctx, struct gl_framebuffer *fb, GLenum attachment)
105 {
106 GLuint i;
107
108 switch (attachment) {
109 case GL_COLOR_ATTACHMENT0_EXT:
110 case GL_COLOR_ATTACHMENT1_EXT:
111 case GL_COLOR_ATTACHMENT2_EXT:
112 case GL_COLOR_ATTACHMENT3_EXT:
113 case GL_COLOR_ATTACHMENT4_EXT:
114 case GL_COLOR_ATTACHMENT5_EXT:
115 case GL_COLOR_ATTACHMENT6_EXT:
116 case GL_COLOR_ATTACHMENT7_EXT:
117 case GL_COLOR_ATTACHMENT8_EXT:
118 case GL_COLOR_ATTACHMENT9_EXT:
119 case GL_COLOR_ATTACHMENT10_EXT:
120 case GL_COLOR_ATTACHMENT11_EXT:
121 case GL_COLOR_ATTACHMENT12_EXT:
122 case GL_COLOR_ATTACHMENT13_EXT:
123 case GL_COLOR_ATTACHMENT14_EXT:
124 case GL_COLOR_ATTACHMENT15_EXT:
125 i = attachment - GL_COLOR_ATTACHMENT0_EXT;
126 if (i >= ctx->Const.MaxColorAttachments) {
127 return NULL;
128 }
129 return &fb->Attachment[BUFFER_COLOR0 + i];
130 case GL_DEPTH_ATTACHMENT_EXT:
131 return &fb->Attachment[BUFFER_DEPTH];
132 case GL_STENCIL_ATTACHMENT_EXT:
133 return &fb->Attachment[BUFFER_STENCIL];
134 default:
135 return NULL;
136 }
137 }
138
139
140 /**
141 * Remove any texture or renderbuffer attached to the given attachment
142 * point. Update reference counts, etc.
143 */
144 void
145 _mesa_remove_attachment(GLcontext *ctx, struct gl_renderbuffer_attachment *att)
146 {
147 if (att->Type == GL_TEXTURE) {
148 ASSERT(att->Texture);
149 if (att->Renderbuffer) {
150 /* delete/remove the 'wrapper' renderbuffer */
151 /* XXX do we really want to do this??? */
152 att->Renderbuffer->Delete(att->Renderbuffer);
153 att->Renderbuffer = NULL;
154 }
155 att->Texture->RefCount--;
156 if (att->Texture->RefCount == 0) {
157 ctx->Driver.DeleteTexture(ctx, att->Texture);
158 }
159 att->Texture = NULL;
160 }
161 else if (att->Type == GL_RENDERBUFFER_EXT) {
162 ASSERT(att->Renderbuffer);
163 ASSERT(!att->Texture);
164 att->Renderbuffer->RefCount--;
165 if (att->Renderbuffer->RefCount == 0) {
166 att->Renderbuffer->Delete(att->Renderbuffer);
167 }
168 att->Renderbuffer = NULL;
169 }
170 att->Type = GL_NONE;
171 att->Complete = GL_TRUE;
172 }
173
174
175 /**
176 * Bind a texture object to an attachment point.
177 * The previous binding, if any, will be removed first.
178 */
179 void
180 _mesa_set_texture_attachment(GLcontext *ctx,
181 struct gl_renderbuffer_attachment *att,
182 struct gl_texture_object *texObj,
183 GLenum texTarget, GLuint level, GLuint zoffset)
184 {
185 _mesa_remove_attachment(ctx, att);
186 att->Type = GL_TEXTURE;
187 att->Texture = texObj;
188 att->TextureLevel = level;
189 if (IS_CUBE_FACE(texTarget)) {
190 att->CubeMapFace = texTarget - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
191 }
192 else {
193 att->CubeMapFace = 0;
194 }
195 att->Zoffset = zoffset;
196 att->Complete = GL_FALSE;
197
198 texObj->RefCount++;
199
200 /* XXX when we attach to a texture, we should probably set the
201 * att->Renderbuffer pointer to a "wrapper renderbuffer" which
202 * makes the texture image look like renderbuffer.
203 */
204 }
205
206
207 /**
208 * Bind a renderbuffer to an attachment point.
209 * The previous binding, if any, will be removed first.
210 */
211 void
212 _mesa_set_renderbuffer_attachment(GLcontext *ctx,
213 struct gl_renderbuffer_attachment *att,
214 struct gl_renderbuffer *rb)
215 {
216 _mesa_remove_attachment(ctx, att);
217 att->Type = GL_RENDERBUFFER_EXT;
218 att->Renderbuffer = rb;
219 att->Texture = NULL; /* just to be safe */
220 att->Complete = GL_FALSE;
221 rb->RefCount++;
222 }
223
224
225 /**
226 * Fallback for ctx->Driver.FramebufferRenderbuffer()
227 * Sets a framebuffer attachment to a particular renderbuffer.
228 * The framebuffer in question is ctx->DrawBuffer.
229 * \sa _mesa_renderbuffer_texture
230 */
231 void
232 _mesa_framebuffer_renderbuffer(GLcontext *ctx,
233 struct gl_renderbuffer_attachment *att,
234 struct gl_renderbuffer *rb)
235 {
236 if (rb) {
237 _mesa_set_renderbuffer_attachment(ctx, att, rb);
238 }
239 else {
240 _mesa_remove_attachment(ctx, att);
241 }
242 }
243
244
245 /**
246 * Test if an attachment point is complete and update its Complete field.
247 * \param format if GL_COLOR, this is a color attachment point,
248 * if GL_DEPTH, this is a depth component attachment point,
249 * if GL_STENCIL, this is a stencil component attachment point.
250 */
251 static void
252 test_attachment_completeness(const GLcontext *ctx, GLenum format,
253 struct gl_renderbuffer_attachment *att)
254 {
255 assert(format == GL_COLOR || format == GL_DEPTH || format == GL_STENCIL);
256
257 /* assume complete */
258 att->Complete = GL_TRUE;
259
260 /* Look for reasons why the attachment might be incomplete */
261 if (att->Type == GL_TEXTURE) {
262 const struct gl_texture_object *texObj = att->Texture;
263 struct gl_texture_image *texImage;
264
265 if (!texObj) {
266 att->Complete = GL_FALSE;
267 return;
268 }
269
270 texImage = texObj->Image[att->CubeMapFace][att->TextureLevel];
271 if (!texImage) {
272 att->Complete = GL_FALSE;
273 return;
274 }
275 if (texImage->Width < 1 || texImage->Height < 1) {
276 att->Complete = GL_FALSE;
277 return;
278 }
279 if (texObj->Target == GL_TEXTURE_3D && att->Zoffset >= texImage->Depth) {
280 att->Complete = GL_FALSE;
281 return;
282 }
283
284 if (format == GL_COLOR) {
285 if (texImage->TexFormat->BaseFormat != GL_RGB &&
286 texImage->TexFormat->BaseFormat != GL_RGBA) {
287 att->Complete = GL_FALSE;
288 return;
289 }
290 }
291 else if (format == GL_DEPTH) {
292 if (texImage->TexFormat->BaseFormat != GL_DEPTH_COMPONENT) {
293 att->Complete = GL_FALSE;
294 return;
295 }
296 }
297 else {
298 /* no such thing as stencil textures */
299 att->Complete = GL_FALSE;
300 return;
301 }
302 }
303 else if (att->Type == GL_RENDERBUFFER_EXT) {
304 if (att->Renderbuffer->Width < 1 || att->Renderbuffer->Height < 1) {
305 att->Complete = GL_FALSE;
306 return;
307 }
308 if (format == GL_COLOR) {
309 if (att->Renderbuffer->_BaseFormat != GL_RGB &&
310 att->Renderbuffer->_BaseFormat != GL_RGBA) {
311 att->Complete = GL_FALSE;
312 return;
313 }
314 }
315 else if (format == GL_DEPTH) {
316 if (att->Renderbuffer->_BaseFormat != GL_DEPTH_COMPONENT) {
317 att->Complete = GL_FALSE;
318 return;
319 }
320 }
321 else {
322 assert(format == GL_STENCIL);
323 if (att->Renderbuffer->_BaseFormat != GL_STENCIL_INDEX) {
324 att->Complete = GL_FALSE;
325 return;
326 }
327 }
328 }
329 else {
330 ASSERT(att->Type == GL_NONE);
331 /* complete */
332 return;
333 }
334 }
335
336
337 /**
338 * Test if the given framebuffer object is complete and update its
339 * Status field with the results.
340 * Also update the framebuffer's Width and Height fields if the
341 * framebuffer is complete.
342 */
343 void
344 _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
345 {
346 GLuint numImages, width = 0, height = 0;
347 GLenum intFormat = GL_NONE;
348 GLuint w = 0, h = 0;
349 GLint i;
350
351 assert(fb->Name != 0);
352
353 numImages = 0;
354 fb->Width = 0;
355 fb->Height = 0;
356
357 /* Start at -2 to more easily loop over all attachment points */
358 for (i = -2; i < (GLint) ctx->Const.MaxColorAttachments; i++) {
359 struct gl_renderbuffer_attachment *att;
360 GLenum f;
361
362 if (i == -2) {
363 att = &fb->Attachment[BUFFER_DEPTH];
364 test_attachment_completeness(ctx, GL_DEPTH, att);
365 if (!att->Complete) {
366 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT;
367 return;
368 }
369 }
370 else if (i == -1) {
371 att = &fb->Attachment[BUFFER_STENCIL];
372 test_attachment_completeness(ctx, GL_STENCIL, att);
373 if (!att->Complete) {
374 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT;
375 return;
376 }
377 }
378 else {
379 att = &fb->Attachment[BUFFER_COLOR0 + i];
380 test_attachment_completeness(ctx, GL_COLOR, att);
381 if (!att->Complete) {
382 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT;
383 return;
384 }
385 }
386
387 if (att->Type == GL_TEXTURE) {
388 w = att->Texture->Image[att->CubeMapFace][att->TextureLevel]->Width;
389 h = att->Texture->Image[att->CubeMapFace][att->TextureLevel]->Height;
390 f = att->Texture->Image[att->CubeMapFace][att->TextureLevel]->Format;
391 numImages++;
392 if (f != GL_RGB && f != GL_RGBA && f != GL_DEPTH_COMPONENT) {
393 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT;
394 return;
395 }
396 }
397 else if (att->Type == GL_RENDERBUFFER_EXT) {
398 w = att->Renderbuffer->Width;
399 h = att->Renderbuffer->Height;
400 f = att->Renderbuffer->InternalFormat;
401 numImages++;
402 }
403 else {
404 assert(att->Type == GL_NONE);
405 continue;
406 }
407
408 if (numImages == 1) {
409 /* set required width, height and format */
410 width = w;
411 height = h;
412 if (i >= 0)
413 intFormat = f;
414 }
415 else {
416 /* check that width, height, format are same */
417 if (w != width || h != height) {
418 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT;
419 return;
420 }
421 if (intFormat != GL_NONE && f != intFormat) {
422 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT;
423 return;
424 }
425 }
426 }
427
428 /* Check that all DrawBuffers are present */
429 for (i = 0; i < ctx->Const.MaxDrawBuffers; i++) {
430 if (fb->ColorDrawBuffer[i] != GL_NONE) {
431 const struct gl_renderbuffer_attachment *att
432 = get_attachment(ctx, fb, fb->ColorDrawBuffer[i]);
433 assert(att);
434 if (att->Type == GL_NONE) {
435 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT;
436 return;
437 }
438 }
439 }
440
441 /* Check that the ReadBuffer is present */
442 if (fb->ColorReadBuffer != GL_NONE) {
443 const struct gl_renderbuffer_attachment *att
444 = get_attachment(ctx, fb, fb->ColorReadBuffer);
445 assert(att);
446 if (att->Type == GL_NONE) {
447 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT;
448 return;
449 }
450 }
451
452 /* Check if any renderbuffer is attached more than once */
453 for (i = 0; i < BUFFER_COUNT - 1; i++) {
454 struct gl_renderbuffer *rb_i = fb->Attachment[i].Renderbuffer;
455 if (rb_i) {
456 GLint j;
457 for (j = i + 1; j < BUFFER_COUNT; j++) {
458 struct gl_renderbuffer *rb_j = fb->Attachment[j].Renderbuffer;
459 if (rb_i == rb_j) {
460 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT;
461 return;
462 }
463 }
464 }
465 }
466
467
468 if (numImages == 0) {
469 fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT;
470 return;
471 }
472
473 /*
474 * If we get here, the framebuffer is complete!
475 */
476 fb->_Status = GL_FRAMEBUFFER_COMPLETE_EXT;
477 fb->Width = w;
478 fb->Height = h;
479 }
480
481
482 GLboolean GLAPIENTRY
483 _mesa_IsRenderbufferEXT(GLuint renderbuffer)
484 {
485 GET_CURRENT_CONTEXT(ctx);
486 ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, GL_FALSE);
487 if (renderbuffer && lookup_renderbuffer(ctx, renderbuffer))
488 return GL_TRUE;
489 else
490 return GL_FALSE;
491 }
492
493
494 void GLAPIENTRY
495 _mesa_BindRenderbufferEXT(GLenum target, GLuint renderbuffer)
496 {
497 struct gl_renderbuffer *newRb, *oldRb;
498 GET_CURRENT_CONTEXT(ctx);
499
500 ASSERT_OUTSIDE_BEGIN_END(ctx);
501 FLUSH_VERTICES(ctx, _NEW_BUFFERS);
502
503 if (target != GL_RENDERBUFFER_EXT) {
504 _mesa_error(ctx, GL_INVALID_ENUM,
505 "glBindRenderbufferEXT(target)");
506 return;
507 }
508
509 if (renderbuffer) {
510 newRb = lookup_renderbuffer(ctx, renderbuffer);
511 if (newRb == &DummyRenderbuffer) {
512 /* ID was reserved, but no real renderbuffer object made yet */
513 newRb = NULL;
514 }
515 if (!newRb) {
516 /* create new renderbuffer object */
517 newRb = ctx->Driver.NewRenderbuffer(ctx, renderbuffer);
518 if (!newRb) {
519 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBindRenderbufferEXT");
520 return;
521 }
522 ASSERT(newRb->AllocStorage);
523 _mesa_HashInsert(ctx->Shared->RenderBuffers, renderbuffer, newRb);
524 }
525 newRb->RefCount++;
526 }
527 else {
528 newRb = NULL;
529 }
530
531 oldRb = ctx->CurrentRenderbuffer;
532 if (oldRb) {
533 oldRb->RefCount--;
534 if (oldRb->RefCount == 0) {
535 oldRb->Delete(oldRb);
536 }
537 }
538
539 ASSERT(newRb != &DummyRenderbuffer);
540
541 ctx->CurrentRenderbuffer = newRb;
542 }
543
544
545 void GLAPIENTRY
546 _mesa_DeleteRenderbuffersEXT(GLsizei n, const GLuint *renderbuffers)
547 {
548 GLint i;
549 GET_CURRENT_CONTEXT(ctx);
550
551 ASSERT_OUTSIDE_BEGIN_END(ctx);
552
553 for (i = 0; i < n; i++) {
554 if (renderbuffers[i] > 0) {
555 struct gl_renderbuffer *rb;
556 rb = lookup_renderbuffer(ctx, renderbuffers[i]);
557 if (rb) {
558 /* remove from hash table immediately, to free the ID */
559 _mesa_HashRemove(ctx->Shared->RenderBuffers, renderbuffers[i]);
560
561 if (rb != &DummyRenderbuffer) {
562 /* But the object will not be freed until it's no longer
563 * bound in any context.
564 */
565 rb->RefCount--;
566 if (rb->RefCount == 0) {
567 rb->Delete(rb);
568 }
569 }
570 }
571 }
572 }
573 }
574
575
576 void GLAPIENTRY
577 _mesa_GenRenderbuffersEXT(GLsizei n, GLuint *renderbuffers)
578 {
579 GET_CURRENT_CONTEXT(ctx);
580 GLuint first;
581 GLint i;
582
583 ASSERT_OUTSIDE_BEGIN_END(ctx);
584
585 if (n < 0) {
586 _mesa_error(ctx, GL_INVALID_VALUE, "glGenRenderbuffersEXT(n)");
587 return;
588 }
589
590 if (!renderbuffers)
591 return;
592
593 first = _mesa_HashFindFreeKeyBlock(ctx->Shared->RenderBuffers, n);
594
595 for (i = 0; i < n; i++) {
596 GLuint name = first + i;
597 renderbuffers[i] = name;
598 /* insert dummy placeholder into hash table */
599 _glthread_LOCK_MUTEX(ctx->Shared->Mutex);
600 _mesa_HashInsert(ctx->Shared->RenderBuffers, name, &DummyRenderbuffer);
601 _glthread_UNLOCK_MUTEX(ctx->Shared->Mutex);
602 }
603 }
604
605
606 /**
607 * Given an internal format token for a render buffer, return the
608 * corresponding base format.
609 * \return one of GL_RGB, GL_RGBA, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT
610 * or zero if error.
611 */
612 static GLenum
613 base_internal_format(GLcontext *ctx, GLenum internalFormat)
614 {
615 switch (internalFormat) {
616 case GL_RGB:
617 case GL_R3_G3_B2:
618 case GL_RGB4:
619 case GL_RGB5:
620 case GL_RGB8:
621 case GL_RGB10:
622 case GL_RGB12:
623 case GL_RGB16:
624 return GL_RGB;
625 case GL_RGBA:
626 case GL_RGBA2:
627 case GL_RGBA4:
628 case GL_RGB5_A1:
629 case GL_RGBA8:
630 case GL_RGB10_A2:
631 case GL_RGBA12:
632 case GL_RGBA16:
633 return GL_RGBA;
634 case GL_STENCIL_INDEX:
635 case GL_STENCIL_INDEX1_EXT:
636 case GL_STENCIL_INDEX4_EXT:
637 case GL_STENCIL_INDEX8_EXT:
638 case GL_STENCIL_INDEX16_EXT:
639 return GL_STENCIL_INDEX;
640 case GL_DEPTH_COMPONENT:
641 case GL_DEPTH_COMPONENT16:
642 case GL_DEPTH_COMPONENT24:
643 case GL_DEPTH_COMPONENT32:
644 return GL_DEPTH_COMPONENT;
645 /* XXX add floating point formats eventually */
646 default:
647 return 0;
648 }
649 }
650
651
652 void GLAPIENTRY
653 _mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
654 GLsizei width, GLsizei height)
655 {
656 struct gl_renderbuffer *rb;
657 GLenum baseFormat;
658 GET_CURRENT_CONTEXT(ctx);
659
660 ASSERT_OUTSIDE_BEGIN_END(ctx);
661 FLUSH_VERTICES(ctx, _NEW_BUFFERS);
662
663 if (target != GL_RENDERBUFFER_EXT) {
664 _mesa_error(ctx, GL_INVALID_ENUM, "glRenderbufferStorageEXT(target)");
665 return;
666 }
667
668 baseFormat = base_internal_format(ctx, internalFormat);
669 if (baseFormat == 0) {
670 _mesa_error(ctx, GL_INVALID_ENUM,
671 "glRenderbufferStorageEXT(internalFormat)");
672 return;
673 }
674
675 if (width < 1 || width > ctx->Const.MaxRenderbufferSize) {
676 _mesa_error(ctx, GL_INVALID_VALUE, "glRenderbufferStorageEXT(width)");
677 return;
678 }
679
680 if (height < 1 || height > ctx->Const.MaxRenderbufferSize) {
681 _mesa_error(ctx, GL_INVALID_VALUE, "glRenderbufferStorageEXT(height)");
682 return;
683 }
684
685 rb = ctx->CurrentRenderbuffer;
686
687 if (!rb) {
688 _mesa_error(ctx, GL_INVALID_OPERATION, "glRenderbufferStorageEXT");
689 return;
690 }
691
692 /* Now allocate the storage */
693 ASSERT(rb->AllocStorage);
694 if (rb->AllocStorage(ctx, rb, internalFormat, width, height)) {
695 /* No error - check/set fields now */
696 assert(rb->Width == width);
697 assert(rb->Height == height);
698 assert(rb->InternalFormat);
699 rb->_BaseFormat = baseFormat;
700 }
701 else {
702 /* Probably ran out of memory - clear the fields */
703 rb->Width = 0;
704 rb->Height = 0;
705 rb->InternalFormat = GL_NONE;
706 rb->_BaseFormat = GL_NONE;
707 }
708
709 /*
710 test_framebuffer_completeness(ctx, fb);
711 */
712 /* XXX if this renderbuffer is attached anywhere, invalidate attachment
713 * points???
714 */
715 }
716
717
718 void GLAPIENTRY
719 _mesa_GetRenderbufferParameterivEXT(GLenum target, GLenum pname, GLint *params)
720 {
721 GET_CURRENT_CONTEXT(ctx);
722
723 ASSERT_OUTSIDE_BEGIN_END(ctx);
724
725 if (target != GL_RENDERBUFFER_EXT) {
726 _mesa_error(ctx, GL_INVALID_ENUM,
727 "glGetRenderbufferParameterivEXT(target)");
728 return;
729 }
730
731 if (!ctx->CurrentRenderbuffer) {
732 _mesa_error(ctx, GL_INVALID_OPERATION,
733 "glGetRenderbufferParameterivEXT");
734 return;
735 }
736
737 switch (pname) {
738 case GL_RENDERBUFFER_WIDTH_EXT:
739 *params = ctx->CurrentRenderbuffer->Width;
740 return;
741 case GL_RENDERBUFFER_HEIGHT_EXT:
742 *params = ctx->CurrentRenderbuffer->Height;
743 return;
744 case GL_RENDERBUFFER_INTERNAL_FORMAT_EXT:
745 *params = ctx->CurrentRenderbuffer->InternalFormat;
746 return;
747 case GL_RENDERBUFFER_RED_SIZE_EXT:
748 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_RGB ||
749 ctx->CurrentRenderbuffer->_BaseFormat == GL_RGBA) {
750 *params = ctx->CurrentRenderbuffer->ComponentSizes[0];
751 }
752 else {
753 *params = 0;
754 }
755 break;
756 case GL_RENDERBUFFER_GREEN_SIZE_EXT:
757 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_RGB ||
758 ctx->CurrentRenderbuffer->_BaseFormat == GL_RGBA) {
759 *params = ctx->CurrentRenderbuffer->ComponentSizes[1];
760 }
761 else {
762 *params = 0;
763 }
764 break;
765 case GL_RENDERBUFFER_BLUE_SIZE_EXT:
766 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_RGB ||
767 ctx->CurrentRenderbuffer->_BaseFormat == GL_RGBA) {
768 *params = ctx->CurrentRenderbuffer->ComponentSizes[2];
769 }
770 else {
771 *params = 0;
772 }
773 break;
774 case GL_RENDERBUFFER_ALPHA_SIZE_EXT:
775 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_RGB ||
776 ctx->CurrentRenderbuffer->_BaseFormat == GL_RGBA) {
777 *params = ctx->CurrentRenderbuffer->ComponentSizes[3];
778 }
779 else {
780 *params = 0;
781 }
782 break;
783 case GL_RENDERBUFFER_DEPTH_SIZE_EXT:
784 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_DEPTH_COMPONENT) {
785 *params = ctx->CurrentRenderbuffer->ComponentSizes[0];
786 }
787 else {
788 *params = 0;
789 }
790 break;
791 case GL_RENDERBUFFER_STENCIL_SIZE_EXT:
792 if (ctx->CurrentRenderbuffer->_BaseFormat == GL_STENCIL_INDEX) {
793 *params = ctx->CurrentRenderbuffer->ComponentSizes[0];
794 }
795 else {
796 *params = 0;
797 }
798 break;
799
800 default:
801 _mesa_error(ctx, GL_INVALID_ENUM,
802 "glGetRenderbufferParameterivEXT(target)");
803 return;
804 }
805 }
806
807
808 GLboolean GLAPIENTRY
809 _mesa_IsFramebufferEXT(GLuint framebuffer)
810 {
811 GET_CURRENT_CONTEXT(ctx);
812 ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, GL_FALSE);
813 if (framebuffer && lookup_framebuffer(ctx, framebuffer))
814 return GL_TRUE;
815 else
816 return GL_FALSE;
817 }
818
819
820 void GLAPIENTRY
821 _mesa_BindFramebufferEXT(GLenum target, GLuint framebuffer)
822 {
823 struct gl_framebuffer *newFb, *newReadFb, *oldFb;
824 GET_CURRENT_CONTEXT(ctx);
825
826 ASSERT_OUTSIDE_BEGIN_END(ctx);
827 FLUSH_VERTICES(ctx, _NEW_BUFFERS);
828
829 if (target != GL_FRAMEBUFFER_EXT) {
830 _mesa_error(ctx, GL_INVALID_ENUM, "glBindFramebufferEXT(target)");
831 return;
832 }
833
834 if (framebuffer) {
835 /* Binding a user-created framebuffer object */
836 newFb = lookup_framebuffer(ctx, framebuffer);
837 if (newFb == &DummyFramebuffer) {
838 /* ID was reserved, but no real framebuffer object made yet */
839 newFb = NULL;
840 }
841 if (!newFb) {
842 /* create new framebuffer object */
843 newFb = ctx->Driver.NewFramebuffer(ctx, framebuffer);
844 if (!newFb) {
845 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBindFramebufferEXT");
846 return;
847 }
848 _mesa_HashInsert(ctx->Shared->FrameBuffers, framebuffer, newFb);
849 }
850 newFb->RefCount++;
851 newReadFb = newFb;
852 }
853 else {
854 /* Binding the window system framebuffer (which was originally set
855 * with MakeCurrent).
856 */
857 newFb = ctx->WinSysDrawBuffer;
858 newReadFb = ctx->WinSysReadBuffer;
859 }
860
861 oldFb = ctx->DrawBuffer;
862 if (oldFb) { /* AND oldFb->Name != 0 */
863 oldFb->RefCount--;
864 if (oldFb->RefCount == 0) {
865 oldFb->Delete(oldFb);
866 }
867 }
868
869 ASSERT(newFb != &DummyFramebuffer);
870
871 /* Note, we set both the GL_DRAW_BUFFER and GL_READ_BUFFER state: */
872 ctx->DrawBuffer = newFb;
873 ctx->ReadBuffer = newReadFb;
874 }
875
876
877 void GLAPIENTRY
878 _mesa_DeleteFramebuffersEXT(GLsizei n, const GLuint *framebuffers)
879 {
880 GLint i;
881 GET_CURRENT_CONTEXT(ctx);
882
883 ASSERT_OUTSIDE_BEGIN_END(ctx);
884
885 for (i = 0; i < n; i++) {
886 if (framebuffers[i] > 0) {
887 struct gl_framebuffer *fb;
888 fb = lookup_framebuffer(ctx, framebuffers[i]);
889 if (fb) {
890 ASSERT(fb == &DummyFramebuffer || fb->Name == framebuffers[i]);
891 /* remove from hash table immediately, to free the ID */
892 _mesa_HashRemove(ctx->Shared->FrameBuffers, framebuffers[i]);
893
894 if (fb != &DummyFramebuffer) {
895 /* But the object will not be freed until it's no longer
896 * bound in any context.
897 */
898 fb->RefCount--;
899 if (fb->RefCount == 0) {
900 fb->Delete(fb);
901 }
902 }
903 }
904 }
905 }
906 }
907
908
909 void GLAPIENTRY
910 _mesa_GenFramebuffersEXT(GLsizei n, GLuint *framebuffers)
911 {
912 GET_CURRENT_CONTEXT(ctx);
913 GLuint first;
914 GLint i;
915
916 ASSERT_OUTSIDE_BEGIN_END(ctx);
917
918 if (n < 0) {
919 _mesa_error(ctx, GL_INVALID_VALUE, "glGenFramebuffersEXT(n)");
920 return;
921 }
922
923 if (!framebuffers)
924 return;
925
926 first = _mesa_HashFindFreeKeyBlock(ctx->Shared->FrameBuffers, n);
927
928 for (i = 0; i < n; i++) {
929 GLuint name = first + i;
930 framebuffers[i] = name;
931 /* insert dummy placeholder into hash table */
932 _glthread_LOCK_MUTEX(ctx->Shared->Mutex);
933 _mesa_HashInsert(ctx->Shared->FrameBuffers, name, &DummyFramebuffer);
934 _glthread_UNLOCK_MUTEX(ctx->Shared->Mutex);
935 }
936 }
937
938
939
940 GLenum GLAPIENTRY
941 _mesa_CheckFramebufferStatusEXT(GLenum target)
942 {
943 GET_CURRENT_CONTEXT(ctx);
944
945 ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, 0);
946
947 if (target != GL_FRAMEBUFFER_EXT) {
948 _mesa_error(ctx, GL_INVALID_ENUM, "glCheckFramebufferStatus(target)");
949 return 0; /* formerly GL_FRAMEBUFFER_STATUS_ERROR_EXT */
950 }
951
952 if (ctx->DrawBuffer->Name == 0) {
953 /* The window system / default framebuffer is always complete */
954 return GL_FRAMEBUFFER_COMPLETE_EXT;
955 }
956
957 _mesa_test_framebuffer_completeness(ctx, ctx->DrawBuffer);
958 return ctx->DrawBuffer->_Status;
959 }
960
961
962
963 /**
964 * Do error checking common to glFramebufferTexture1D/2D/3DEXT.
965 * \return GL_TRUE if any error, GL_FALSE otherwise
966 */
967 static GLboolean
968 error_check_framebuffer_texture(GLcontext *ctx, GLuint dims,
969 GLenum target, GLenum attachment,
970 GLenum textarget, GLuint texture, GLint level)
971 {
972 ASSERT(dims >= 1 && dims <= 3);
973
974 if (target != GL_FRAMEBUFFER_EXT) {
975 _mesa_error(ctx, GL_INVALID_ENUM,
976 "glFramebufferTexture%dDEXT(target)", dims);
977 return GL_TRUE;
978 }
979
980 /* check framebuffer binding */
981 if (ctx->DrawBuffer->Name == 0) {
982 _mesa_error(ctx, GL_INVALID_OPERATION,
983 "glFramebufferTexture%dDEXT", dims);
984 return GL_TRUE;
985 }
986
987 /* only check textarget, level if texture ID is non-zero */
988 if (texture) {
989 if ((dims == 1 && textarget != GL_TEXTURE_1D) ||
990 (dims == 3 && textarget != GL_TEXTURE_3D) ||
991 (dims == 2 && textarget != GL_TEXTURE_2D &&
992 textarget != GL_TEXTURE_RECTANGLE_ARB &&
993 !IS_CUBE_FACE(textarget))) {
994 _mesa_error(ctx, GL_INVALID_VALUE,
995 "glFramebufferTexture%dDEXT(textarget)", dims);
996 return GL_TRUE;
997 }
998
999 if ((level < 0) || level >= _mesa_max_texture_levels(ctx, textarget)) {
1000 _mesa_error(ctx, GL_INVALID_VALUE,
1001 "glFramebufferTexture%dDEXT(level)", dims);
1002 return GL_TRUE;
1003 }
1004 }
1005
1006 return GL_FALSE;
1007 }
1008
1009
1010 void GLAPIENTRY
1011 _mesa_FramebufferTexture1DEXT(GLenum target, GLenum attachment,
1012 GLenum textarget, GLuint texture, GLint level)
1013 {
1014 struct gl_renderbuffer_attachment *att;
1015 struct gl_texture_object *texObj;
1016 GET_CURRENT_CONTEXT(ctx);
1017
1018 ASSERT_OUTSIDE_BEGIN_END(ctx);
1019 FLUSH_VERTICES(ctx, _NEW_BUFFERS); /* XXX check */
1020
1021 if (error_check_framebuffer_texture(ctx, 1, target, attachment,
1022 textarget, texture, level))
1023 return;
1024
1025 ASSERT(textarget == GL_TEXTURE_1D);
1026
1027 att = get_attachment(ctx, ctx->DrawBuffer, attachment);
1028 if (att == NULL) {
1029 _mesa_error(ctx, GL_INVALID_ENUM,
1030 "glFramebufferTexture1DEXT(attachment)");
1031 return;
1032 }
1033
1034 if (texture) {
1035 texObj = (struct gl_texture_object *)
1036 _mesa_HashLookup(ctx->Shared->TexObjects, texture);
1037 if (!texObj) {
1038 _mesa_error(ctx, GL_INVALID_VALUE,
1039 "glFramebufferTexture1DEXT(texture)");
1040 return;
1041 }
1042 if (texObj->Target != textarget) {
1043 _mesa_error(ctx, GL_INVALID_OPERATION, /* XXX correct error? */
1044 "glFramebufferTexture1DEXT(texture target)");
1045 return;
1046 }
1047 }
1048 else {
1049 /* remove texture attachment */
1050 texObj = NULL;
1051 }
1052 ctx->Driver.RenderbufferTexture(ctx, att, texObj, textarget, level, 0);
1053 }
1054
1055
1056 void GLAPIENTRY
1057 _mesa_FramebufferTexture2DEXT(GLenum target, GLenum attachment,
1058 GLenum textarget, GLuint texture, GLint level)
1059 {
1060 struct gl_renderbuffer_attachment *att;
1061 struct gl_texture_object *texObj;
1062 GET_CURRENT_CONTEXT(ctx);
1063
1064 ASSERT_OUTSIDE_BEGIN_END(ctx);
1065 FLUSH_VERTICES(ctx, _NEW_BUFFERS); /* XXX check */
1066
1067 if (error_check_framebuffer_texture(ctx, 2, target, attachment,
1068 textarget, texture, level))
1069 return;
1070
1071 ASSERT(textarget == GL_TEXTURE_2D ||
1072 textarget == GL_TEXTURE_RECTANGLE_ARB ||
1073 IS_CUBE_FACE(textarget));
1074
1075 att = get_attachment(ctx, ctx->DrawBuffer, attachment);
1076 if (att == NULL) {
1077 _mesa_error(ctx, GL_INVALID_ENUM,
1078 "glFramebufferTexture2DEXT(attachment)");
1079 return;
1080 }
1081
1082 if (texture) {
1083 texObj = (struct gl_texture_object *)
1084 _mesa_HashLookup(ctx->Shared->TexObjects, texture);
1085 if (!texObj) {
1086 _mesa_error(ctx, GL_INVALID_VALUE,
1087 "glFramebufferTexture2DEXT(texture)");
1088 return;
1089 }
1090 if ((texObj->Target == GL_TEXTURE_2D && textarget != GL_TEXTURE_2D) ||
1091 (texObj->Target == GL_TEXTURE_RECTANGLE_ARB
1092 && textarget != GL_TEXTURE_RECTANGLE_ARB) ||
1093 (texObj->Target == GL_TEXTURE_CUBE_MAP
1094 && !IS_CUBE_FACE(textarget))) {
1095 _mesa_error(ctx, GL_INVALID_OPERATION, /* XXX correct error? */
1096 "glFramebufferTexture2DEXT(texture target)");
1097 return;
1098 }
1099 }
1100 else {
1101 /* remove texture attachment */
1102 texObj = NULL;
1103 }
1104 ctx->Driver.RenderbufferTexture(ctx, att, texObj, textarget, level, 0);
1105 }
1106
1107
1108 void GLAPIENTRY
1109 _mesa_FramebufferTexture3DEXT(GLenum target, GLenum attachment,
1110 GLenum textarget, GLuint texture,
1111 GLint level, GLint zoffset)
1112 {
1113 struct gl_renderbuffer_attachment *att;
1114 struct gl_texture_object *texObj;
1115 GET_CURRENT_CONTEXT(ctx);
1116
1117 ASSERT_OUTSIDE_BEGIN_END(ctx);
1118 FLUSH_VERTICES(ctx, _NEW_BUFFERS); /* XXX check */
1119
1120 if (error_check_framebuffer_texture(ctx, 3, target, attachment,
1121 textarget, texture, level))
1122 return;
1123
1124 ASSERT(textarget == GL_TEXTURE_3D);
1125
1126 att = get_attachment(ctx, ctx->DrawBuffer, attachment);
1127 if (att == NULL) {
1128 _mesa_error(ctx, GL_INVALID_ENUM,
1129 "glFramebufferTexture1DEXT(attachment)");
1130 return;
1131 }
1132
1133 if (texture) {
1134 const GLint maxSize = 1 << (ctx->Const.Max3DTextureLevels - 1);
1135 texObj = (struct gl_texture_object *)
1136 _mesa_HashLookup(ctx->Shared->TexObjects, texture);
1137 if (!texObj) {
1138 _mesa_error(ctx, GL_INVALID_VALUE,
1139 "glFramebufferTexture3DEXT(texture)");
1140 return;
1141 }
1142 if (texObj->Target != textarget) {
1143 _mesa_error(ctx, GL_INVALID_OPERATION, /* XXX correct error? */
1144 "glFramebufferTexture3DEXT(texture target)");
1145 return;
1146 }
1147 if (zoffset < 0 || zoffset >= maxSize) {
1148 _mesa_error(ctx, GL_INVALID_VALUE,
1149 "glFramebufferTexture3DEXT(zoffset)");
1150 return;
1151 }
1152 }
1153 else {
1154 /* remove texture attachment */
1155 texObj = NULL;
1156 }
1157 ctx->Driver.RenderbufferTexture(ctx, att, texObj, textarget,
1158 level, zoffset);
1159 }
1160
1161
1162 void GLAPIENTRY
1163 _mesa_FramebufferRenderbufferEXT(GLenum target, GLenum attachment,
1164 GLenum renderbufferTarget,
1165 GLuint renderbuffer)
1166 {
1167 struct gl_renderbuffer_attachment *att;
1168 struct gl_renderbuffer *rb;
1169 GET_CURRENT_CONTEXT(ctx);
1170
1171 ASSERT_OUTSIDE_BEGIN_END(ctx);
1172 FLUSH_VERTICES(ctx, _NEW_BUFFERS);
1173
1174 if (target != GL_FRAMEBUFFER_EXT) {
1175 _mesa_error(ctx, GL_INVALID_ENUM,
1176 "glFramebufferRenderbufferEXT(target)");
1177 return;
1178 }
1179
1180 if (renderbufferTarget != GL_RENDERBUFFER_EXT) {
1181 _mesa_error(ctx, GL_INVALID_ENUM,
1182 "glFramebufferRenderbufferEXT(renderbufferTarget)");
1183 return;
1184 }
1185
1186 if (ctx->DrawBuffer->Name == 0) {
1187 /* Can't attach new renderbuffers to a window system framebuffer */
1188 _mesa_error(ctx, GL_INVALID_OPERATION, "glFramebufferRenderbufferEXT");
1189 return;
1190 }
1191
1192 att = get_attachment(ctx, ctx->DrawBuffer, attachment);
1193 if (att == NULL) {
1194 _mesa_error(ctx, GL_INVALID_ENUM,
1195 "glFramebufferRenderbufferEXT(attachment)");
1196 return;
1197 }
1198
1199 if (renderbuffer) {
1200 rb = lookup_renderbuffer(ctx, renderbuffer);
1201 if (!rb) {
1202 _mesa_error(ctx, GL_INVALID_OPERATION,
1203 "glFramebufferRenderbufferEXT(renderbuffer)");
1204 return;
1205 }
1206 }
1207 else {
1208 /* remove renderbuffer attachment */
1209 rb = NULL;
1210 }
1211
1212 assert(ctx->Driver.FramebufferRenderbuffer);
1213 ctx->Driver.FramebufferRenderbuffer(ctx, att, rb);
1214
1215 _mesa_update_framebuffer_visual(ctx->DrawBuffer);
1216 }
1217
1218
1219 void GLAPIENTRY
1220 _mesa_GetFramebufferAttachmentParameterivEXT(GLenum target, GLenum attachment,
1221 GLenum pname, GLint *params)
1222 {
1223 const struct gl_renderbuffer_attachment *att;
1224 GET_CURRENT_CONTEXT(ctx);
1225
1226 ASSERT_OUTSIDE_BEGIN_END(ctx);
1227
1228 if (target != GL_FRAMEBUFFER_EXT) {
1229 _mesa_error(ctx, GL_INVALID_ENUM,
1230 "glGetFramebufferAttachmentParameterivEXT(target)");
1231 return;
1232 }
1233
1234 if (ctx->DrawBuffer->Name == 0) {
1235 _mesa_error(ctx, GL_INVALID_OPERATION,
1236 "glGetFramebufferAttachmentParameterivEXT");
1237 return;
1238 }
1239
1240 att = get_attachment(ctx, ctx->DrawBuffer, attachment);
1241 if (att == NULL) {
1242 _mesa_error(ctx, GL_INVALID_ENUM,
1243 "glGetFramebufferAttachmentParameterivEXT(attachment)");
1244 return;
1245 }
1246
1247 switch (pname) {
1248 case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT:
1249 *params = att->Type;
1250 return;
1251 case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT:
1252 if (att->Type == GL_RENDERBUFFER_EXT) {
1253 *params = att->Renderbuffer->Name;
1254 }
1255 else if (att->Type == GL_TEXTURE) {
1256 *params = att->Texture->Name;
1257 }
1258 else {
1259 _mesa_error(ctx, GL_INVALID_ENUM,
1260 "glGetFramebufferAttachmentParameterivEXT(pname)");
1261 }
1262 return;
1263 case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT:
1264 if (att->Type == GL_TEXTURE) {
1265 *params = att->TextureLevel;
1266 }
1267 else {
1268 _mesa_error(ctx, GL_INVALID_ENUM,
1269 "glGetFramebufferAttachmentParameterivEXT(pname)");
1270 }
1271 return;
1272 case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT:
1273 if (att->Type == GL_TEXTURE) {
1274 *params = GL_TEXTURE_CUBE_MAP_POSITIVE_X + att->CubeMapFace;
1275 }
1276 else {
1277 _mesa_error(ctx, GL_INVALID_ENUM,
1278 "glGetFramebufferAttachmentParameterivEXT(pname)");
1279 }
1280 return;
1281 case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT:
1282 if (att->Type == GL_TEXTURE) {
1283 *params = att->Zoffset;
1284 }
1285 else {
1286 _mesa_error(ctx, GL_INVALID_ENUM,
1287 "glGetFramebufferAttachmentParameterivEXT(pname)");
1288 }
1289 return;
1290 default:
1291 _mesa_error(ctx, GL_INVALID_ENUM,
1292 "glGetFramebufferAttachmentParameterivEXT(pname)");
1293 return;
1294 }
1295 }
1296
1297
1298 void GLAPIENTRY
1299 _mesa_GenerateMipmapEXT(GLenum target)
1300 {
1301 struct gl_texture_unit *texUnit;
1302 struct gl_texture_object *texObj;
1303 GET_CURRENT_CONTEXT(ctx);
1304
1305 ASSERT_OUTSIDE_BEGIN_END(ctx);
1306
1307 switch (target) {
1308 case GL_TEXTURE_1D:
1309 case GL_TEXTURE_2D:
1310 case GL_TEXTURE_3D:
1311 case GL_TEXTURE_CUBE_MAP:
1312 /* OK, legal value */
1313 break;
1314 default:
1315 _mesa_error(ctx, GL_INVALID_ENUM, "glGenerateMipmapEXT(target)");
1316 return;
1317 }
1318
1319 texUnit = &ctx->Texture.Unit[ctx->Texture.CurrentUnit];
1320 texObj = _mesa_select_tex_object(ctx, texUnit, target);
1321
1322 /* XXX this might not handle cube maps correctly */
1323 _mesa_generate_mipmap(ctx, target, texUnit, texObj);
1324 }