#include <GL/gl.h> #include <GL/glext.h> #include <libavutil/avutil.h> #include <libavutil/pixfmt.h> #include <libswscale/swscale.h> #if LIBAVUTIL_VERSION_MAJOR < 54 #define AV_PIX_FMT_RGBA PIX_FMT_RGBA #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 #define AV_PIX_FMT_BGRA PIX_FMT_BGRA #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 #endif #define SCALAR_REF_DATA(obj) (SvROK(obj) && SvPOK(SvRV(obj))? (void*)SvPVX(SvRV(obj)) : (void*)0) #define SCALAR_REF_LEN(obj) (SvROK(obj) && SvPOK(SvRV(obj))? SvCUR(SvRV(obj)) : 0) extern void glGenerateMipmap(int); /* This gets called by Moo */ int _build_tx_id(HV *self) { GLuint tx_id; glGenTextures(1, &tx_id); return tx_id; } /* This is our shortcut to bypass Moo from C. The more correct thing to do would be set up a call * stack and call $self->_build_tx_id in case someone overrode it in a subclass. */ int _lazy_build_tx_id(HV *self) { SV **field_p= hv_fetch(self, "tx_id", 5, 1); GLuint tx_id; if (!field_p || !*field_p) croak("Can't store results in supplied hash"); if (SvIOK(*field_p)) { tx_id= SvIV(*field_p); } else { tx_id= _build_tx_id(self); sv_setiv(*field_p, tx_id); } return tx_id; } void DESTROY(HV *self) { GLuint tx_id; SV **field= hv_fetch(self, "tx_id", 5, 0); if (field && *field && SvIOK(*field)) { tx_id= SvIV(*field); if (tx_id) glDeleteTextures(1, &tx_id); } } /* This function operates on the idea that a power of two texture composed of * RGB or RGBA pixels must either be 4*4*4...*4 or 4*4*4...*3 bytes long. * So, it will either be a clean power of 4, or a power of 4 times 3. * This iteratively divides by 4, then checks to see if the result is 1 or 3. */ int _dimension_from_filesize(int filesize, int *has_alpha_out) { int dim= 1, size= filesize; if (size) { /* Count size's powers of 4, in dim */ while ((size & 3) == 0) { size >>= 2; dim <<= 1; } } if (size != 1 && size != 3) croak("File length 0x%X is not a power of 2 quare of pixels", size); if (size == 1) { /* RGBA, even power of 4 bytes */ *has_alpha_out= 1; return dim >> 1; } else { /* RGB */ *has_alpha_out= 0; return dim; } } /* Reading from perl hashes is annoying. This simplified function only returns * non-NULL if the key existed and the value was defined. */ SV *_fetch_if_defined(HV *self, const char *field, int len) { SV **field_p= hv_fetch(self, field, len, 0); return (field_p && *field_p && SvOK(*field_p)) ? *field_p : NULL; } void _load_rgb_square(HV *self, SV *mmap, int is_bgr) { SV *sv; void *data= SCALAR_REF_DATA(mmap); int len= SCALAR_REF_LEN(mmap); const char *ver; int major, minor; SV *mipmap_p= _fetch_if_defined(self, "mipmap", 6); SV *wrap_s_p= _fetch_if_defined(self, "wrap_s", 6); SV *wrap_t_p= _fetch_if_defined(self, "wrap_t", 6); SV *min_filter_p= _fetch_if_defined(self, "min_filter", 10); SV *mag_filter_p= _fetch_if_defined(self, "mag_filter", 10); int has_alpha= 0; int dim= _dimension_from_filesize(len, &has_alpha); int gl_fmt= is_bgr? ( has_alpha? GL_BGRA : GL_BGR ) : ( has_alpha? GL_RGBA : GL_RGB ); int gl_internal_fmt= has_alpha? GL_RGBA : GL_RGB; /* use mipmaps if the user set it to true, or if the min_filter uses a mipmap, and default in absence of any user prefs is true. */ int with_mipmaps= mipmap_p? SvTRUE(mipmap_p) : !min_filter_p? 1 : SvIV(min_filter_p) == GL_NEAREST || SvIV(min_filter_p) == GL_LINEAR ? 0 : 1; if (!data || !len) croak("Expected non-empty scalar-ref pixel buffer"); /* Ensure the OpenGL context is initialized */ //call_pv("OpenGL::Sandbox::_ensure_context", G_VOID | G_NOARGS | G_EVAL); //if (SvTRUE(ERRSV)) // croak(NULL); /* Mipmap strategy depends on version of GL. Supposedly this GetString is more compatible than GetInteger(GL_VERSION_MAJOR) */ ver= (const char *) glGetString(GL_VERSION); if (!ver || sscanf(ver, "%d.%d", &major, &minor) != 2) croak("Can't get GL_VERSION"); /* Bind texture */ glBindTexture(GL_TEXTURE_2D, _lazy_build_tx_id(self)); if (with_mipmaps) { if (major < 3) { glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); if (mag_filter_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, SvIV(mag_filter_p)); if (min_filter_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, SvIV(min_filter_p)); } } else { if (mag_filter_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, SvIV(mag_filter_p)); /* this one needs overridden even if user didn't request it, because default uses mipmaps */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_p? SvIV(min_filter_p) : GL_LINEAR); /* and inform opengl that this is the only mipmap level */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); } glTexImage2D(GL_TEXTURE_2D, 0, gl_internal_fmt, dim, dim, 0, gl_fmt, GL_UNSIGNED_BYTE, data); if (with_mipmaps && major >= 3) { /* glEnable(GL_TEXTURE_2D); /* correct bug in ATI, accoridng to Khronos FAQ */ glGenerateMipmap(GL_TEXTURE_2D); /* examples show setting these after mipmap generation. Does it matter? */ if (mag_filter_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, SvIV(mag_filter_p)); if (min_filter_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, SvIV(min_filter_p)); } if (wrap_s_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SvIV(wrap_s_p)); if (wrap_t_p) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SvIV(wrap_t_p)); /* update attributes */ if (!hv_store(self, "width", 5, sv=newSViv(dim), 0) || !hv_store(self, "height", 6, sv=newSViv(dim), 0) || !hv_store(self, "pow2_size", 9, sv=newSViv(dim), 0) || !hv_store(self, "has_alpha", 9, sv=newSViv(has_alpha? 1 : 0), 0) ) { if (sv) sv_2mortal(sv); croak("Can't store results in supplied hash"); } return; } int _round_up_pow2(long dim) { --dim; dim |= dim >> 32; dim |= dim >> 16; dim |= dim >> 8; dim |= dim >> 4; dim |= dim >> 2; dim |= dim >> 1; return ++dim; } SV* _rescale_to_pow2_square(int width, int height, int has_alpha, int want_bgr, SV *sref) { struct SwsContext *sws= NULL; SV *ret= NULL; void *data= SCALAR_REF_DATA(sref); int len= SCALAR_REF_LEN(sref); int px_size= has_alpha? 4 : 3; int dim= _round_up_pow2(width > height? width : height); const uint8_t *src_planes[4]= { data,0,0,0 }; int src_stride[4]= { width*px_size,0,0,0 }; uint8_t *dst_planes[4]= { 0,0,0,0 }; int dst_stride[4]= { dim*px_size,0,0,0 }; if (!data || !len) croak("Expected non-empty scalar-ref pixel buffer"); if (width * height * px_size != len) croak("Size of scalar ref disagrees with rectangle dimensions: %d * %d * %d != %d", width, height, px_size, len); /* rescale to square */ sws= sws_getCachedContext(sws, width, height, has_alpha? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24, dim, dim, want_bgr? (has_alpha? AV_PIX_FMT_BGRA : AV_PIX_FMT_BGR24) : (has_alpha? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24), SWS_BICUBIC, NULL, NULL, NULL); if (!sws) croak("can't initialize resize context"); /* allocate a "mortal" scalar into which we write the new image */ ret= sv_2mortal(newSV(dim*dim*px_size)); sv_setpvn(ret, "", 0); SvGROW(ret, dim*dim*px_size); SvCUR_set(ret, dim*dim*px_size); dst_planes[0]= (uint8_t*) SvPVX(ret); /* perform the rescale */ sws_scale(sws, src_planes, src_stride, 0, height, dst_planes, dst_stride); sws_freeContext(sws); /* return a ref to the scalar, to avoid making a copy */ return newRV_inc(ret); } void _bind_tx(HV *self, ...) { int target= GL_TEXTURE_2D; Inline_Stack_Vars; if (Inline_Stack_Items > 1) target= SvIV(Inline_Stack_Item(1)); glBindTexture(_lazy_build_tx_id(self), target); Inline_Stack_Void; }