11#ifndef GAMP_RENDER_GL_TEXTURE_TEXTURE_HPP_
12#define GAMP_RENDER_GL_TEXTURE_TEXTURE_HPP_
156 GLenum m_imageTarget;
160 bool m_ownsTextureID;
168 bool m_mustFlipVertically;
171 bool m_usingAutoMipmapGeneration;
177 size_t m_estimatedMemorySize;
179 constexpr static bool DEBUG_MODE =
false;
180 constexpr static bool VERBOSE =
false;
184 std::string r(
"Texture[target ");
186 if( m_target == m_imageTarget ) {
189 r.append(
", name ").append(std::to_string(m_texID)).append(
", {");
190 r.append(m_imgSize.toString()).append(
"} / {").append(m_texSize.toString()).append(
"}, y-flip ")
192 .append(std::to_string(m_estimatedMemorySize)).append(
" bytes]");
197 : m_target(0), m_imageTarget(0), m_texID(0), m_ownsTextureID(true),
198 m_texSize(), m_imgSize(),
199 m_aspectRatio(1), m_mustFlipVertically(false), m_usingAutoMipmapGeneration(false),
200 m_coords(), m_estimatedMemorySize(0)
212 : m_target(
target), m_imageTarget(
target), m_texID(0), m_ownsTextureID(true),
213 m_texSize(), m_imgSize(),
214 m_aspectRatio(1), m_mustFlipVertically(false), m_usingAutoMipmapGeneration(false),
215 m_coords(), m_estimatedMemorySize(0)
248 : m_target(
target), m_imageTarget(
target), m_texID(textureID), m_ownsTextureID(ownsTextureID),
252 m_coords(), m_estimatedMemorySize(0)
255 if ( 0 == m_texID ) {
295 if( !
gl.glProfile().isGLcore() && GL_TEXTURE_EXTERNAL_OES != m_target) {
296 ::glEnable(m_target);
322 if( !
gl.glProfile().isGLcore() && GL_TEXTURE_EXTERNAL_OES != m_target) {
323 ::glDisable(m_target);
342 validateTexID(
gl,
true);
343 ::glBindTexture(m_target, m_texID);
353 if( 0 != m_texID && m_ownsTextureID ) {
354 ::glDeleteTextures(1, &m_texID);
364 constexpr GLenum
target() const noexcept {
return m_target; }
371 constexpr GLenum
imageTarget() const noexcept {
return m_imageTarget; }
395 constexpr float aspectRatio() const noexcept {
return m_aspectRatio; }
419 if (GL_TEXTURE_RECTANGLE_ARB == m_imageTarget) {
420 if (m_mustFlipVertically) {
421 return TextureCoords(
float(bl.x),
float(m_texSize.y - bl.y),
float(tr.x),
float(m_texSize.y - tr.y));
426 jau::math::Point2f t_bl(
float(bl.x)/
float(m_texSize.x),
float(bl.y)/
float(m_texSize.y));
427 jau::math::Point2f t_tr(
float(tr.x)/
float(m_texSize.x),
float(tr.y)/
float(m_texSize.y));
429 if (m_mustFlipVertically) {
430 const float yMax = (float) m_imgSize.y / (
float) m_texSize.y;
431 t_bl.
y = yMax - t_bl.
y;
432 t_tr.
y = yMax - t_tr.
y;
464 if( v != m_mustFlipVertically ) {
465 m_mustFlipVertically = v;
478 validateTexID(
gl,
true);
480 m_imgSize =
data.getSize();
481 m_aspectRatio = (float) m_imgSize.x / (
float) m_imgSize.y;
482 m_mustFlipVertically =
data.getMustFlipVertically();
485 int texParamTarget = m_target;
488 bool haveAutoMipmapGeneration =
501 if (!isPOT && !haveNPOT(
gl)) {
502 haveAutoMipmapGeneration =
false;
505 bool expandingCompressedTexture =
false;
507 if (
data.getMipmap() && !haveAutoMipmapGeneration) {
515 texHeight = imgHeight;
516 texTarget =
GL.GL_TEXTURE_2D;
520 if (!done && preferTexRect(
gl) && !isPOT &&
521 haveTexRect(
gl) && !
data.isDataCompressed() && !
gl.isGL3() && !
gl.isGLES()) {
524 System.err.println(
"Using GL_ARB_texture_rectangle preferentially on this hardware");
528 texHeight = imgHeight;
529 texTarget = GL2.GL_TEXTURE_RECTANGLE_ARB;
533 if (!done && (isPOT || haveNPOT(
gl))) {
536 System.err.println(
"Power-of-two texture");
538 System.err.println(
"Using GL_ARB_texture_non_power_of_two");
543 texHeight = imgHeight;
544 texTarget =
GL.GL_TEXTURE_2D;
548 if (!done && haveTexRect(
gl) && !
data.isDataCompressed() && !
gl.isGL3() && !
gl.isGLES()) {
551 System.err.println(
"Using GL_ARB_texture_rectangle");
555 texHeight = imgHeight;
556 texTarget = GL2.GL_TEXTURE_RECTANGLE_ARB;
567 if (
data.isDataCompressed()) {
568 if (
data.getMipmapData() != null) {
574 throw new GLException(
"Mipmapped non-power-of-two compressed textures only supported on OpenGL 2.0 hardware (GL_ARB_texture_non_power_of_two)");
577 expandingCompressedTexture =
true;
581 System.err.println(
"Expanding texture to power-of-two dimensions");
584 if (
data.getBorder() != 0) {
585 throw new RuntimeException(
"Scaling up a non-power-of-two texture which has a border won't work");
587 texWidth = Bitfield.Util.roundToPowerOf2(imgWidth);
588 texHeight = Bitfield.Util.roundToPowerOf2(imgHeight);
589 texTarget =
GL.GL_TEXTURE_2D;
591 texParamTarget = texTarget;
595 if (targetOverride != 0) {
599 throw new GLException(
"Override of target failed; no target specified yet");
601 texTarget = targetOverride;
602 texParamTarget = this.
target;
603 gl.glBindTexture(texParamTarget, texID);
605 gl.glBindTexture(texTarget, texID);
608 if (
data.getMipmap() && !haveAutoMipmapGeneration) {
609 final int[] align =
new int[1];
610 gl.glGetIntegerv(
GL.GL_UNPACK_ALIGNMENT, align, 0);
611 gl.glPixelStorei(
GL.GL_UNPACK_ALIGNMENT,
data.getAlignment());
613 if (
data.isDataCompressed()) {
614 throw new GLException(
"May not request mipmap generation for compressed textures");
619 final GLU glu = GLU.createGLU(
gl);
620 glu.gluBuild2DMipmaps(texTarget,
data.getInternalFormat(),
622 data.getPixelFormat(),
data.getPixelType(),
data.getBuffer());
624 gl.glPixelStorei(
GL.GL_UNPACK_ALIGNMENT, align[0]);
627 checkCompressedTextureExtensions(
gl,
data);
628 final Buffer[] mipmapData =
data.getMipmapData();
629 if (mipmapData != null) {
630 int width = texWidth;
631 int height = texHeight;
632 for (
int i = 0; i < mipmapData.length; i++) {
633 if (
data.isDataCompressed()) {
636 gl.glCompressedTexImage2D(texTarget, i,
data.getInternalFormat(),
637 width, height,
data.getBorder(),
638 mipmapData[i].remaining(), mipmapData[i]);
641 ::glTexImage2D(texTarget, i,
data.getInternalFormat(),
642 width, height,
data.getBorder(),
643 data.getPixelFormat(),
data.getPixelType(), null);
644 updateSubImageImpl(
gl,
data, texTarget, i, 0, 0, 0, 0,
data.getWidth(),
data.getHeight());
647 width = Math.max(width / 2, 1);
648 height = Math.max(height / 2, 1);
651 if (
data.isDataCompressed()) {
652 if (!expandingCompressedTexture) {
655 gl.glCompressedTexImage2D(texTarget, 0,
data.getInternalFormat(),
656 texWidth, texHeight,
data.getBorder(),
657 data.getBuffer().capacity(),
data.getBuffer());
659 final ByteBuffer buf = DDSImage.allocateBlankBuffer(texWidth,
661 data.getInternalFormat());
662 gl.glCompressedTexImage2D(texTarget, 0,
data.getInternalFormat(),
663 texWidth, texHeight,
data.getBorder(),
664 buf.capacity(), buf);
665 updateSubImageImpl(
gl,
data, texTarget, 0, 0, 0, 0, 0,
data.getWidth(),
data.getHeight());
668 if (
data.getMipmap() && haveAutoMipmapGeneration &&
gl.isGL2ES1()) {
673 gl.glTexParameteri(texParamTarget, GL2ES1.GL_GENERATE_MIPMAP,
GL.GL_TRUE);
674 usingAutoMipmapGeneration =
true;
677 gl.glTexImage2D(texTarget, 0,
data.getInternalFormat(),
678 texWidth, texHeight,
data.getBorder(),
679 data.getPixelFormat(),
data.getPixelType(), null);
680 updateSubImageImpl(
gl,
data, texTarget, 0, 0, 0, 0, 0,
data.getWidth(),
data.getHeight());
685 final int minFilter = (
data.getMipmap() ?
GL.GL_LINEAR_MIPMAP_LINEAR :
GL.GL_LINEAR);
686 final int magFilter =
GL.GL_LINEAR;
690 if (texTarget != GL2.GL_TEXTURE_RECTANGLE_ARB) {
691 gl.glTexParameteri(texParamTarget,
GL.GL_TEXTURE_MIN_FILTER, minFilter);
692 gl.glTexParameteri(texParamTarget,
GL.GL_TEXTURE_MAG_FILTER, magFilter);
693 gl.glTexParameteri(texParamTarget,
GL.GL_TEXTURE_WRAP_S, wrapMode);
694 gl.glTexParameteri(texParamTarget,
GL.GL_TEXTURE_WRAP_T, wrapMode);
695 if (this.
target ==
GL.GL_TEXTURE_CUBE_MAP) {
696 gl.glTexParameteri(texParamTarget, GL2ES2.GL_TEXTURE_WRAP_R, wrapMode);
703 (this.
target ==
GL.GL_TEXTURE_2D) ||
704 (
this.target == GL2.GL_TEXTURE_RECTANGLE_ARB)) {
709 estimatedMemorySize =
data.getEstimatedMemorySize();
734 if (m_usingAutoMipmapGeneration && mipmapLevel != 0) {
740 updateSubImageImpl(
gl,
data,
target, mipmapLevel, x, y, 0, 0,
data.getWidth(),
data.getHeight());
776 int width,
int height) {
777 if (
data.isDataCompressed()) {
780 if (m_usingAutoMipmapGeneration && mipmapLevel != 0) {
786 updateSubImageImpl(
gl,
data,
target, mipmapLevel, dstx, dsty, srcx, srcy, width, height);
800 ::glTexParameterf(
target, parameterName, value);
812 ::glTexParameterfv(
target, parameterName, params);
827 ::glTexParameteri(
target, parameterName, value);
839 ::glTexParameteriv(
target, parameterName, params);
854 validateTexID(
gl,
false);
889 return usingAutoMipmapGeneration;
893 void updateTexCoords() {
894 if ( GL2.GL_TEXTURE_RECTANGLE_ARB ==
imageTarget ) {
898 coords =
new TextureCoords(0, 0, imgWidth, imgHeight);
902 coords =
new TextureCoords(0,
903 (
float) imgHeight / (
float) texHeight,
904 (
float) imgWidth / (
float) texWidth,
908 coords =
new TextureCoords(0,
910 (
float) imgWidth / (
float) texWidth,
911 (
float) imgHeight / (
float) texHeight
917 void updateSubImageImpl(
const GL& gl,
const TextureData& data,
int newTarget,
int mipmapLevel,
919 int srcx,
int srcy,
int width,
int height) {
920 data.setHaveEXTABGR(gl.isExtensionAvailable(GLExtensions.EXT_abgr));
921 data.setHaveGL12(gl.isExtensionAvailable(GLExtensions.VERSION_1_2));
923 Buffer buffer = data.getBuffer();
924 if (buffer == null && data.getMipmapData() == null) {
929 int rowlen = data.getRowLength();
930 int dataWidth = data.getWidth();
931 int dataHeight = data.getHeight();
932 if (data.getMipmapData() != null) {
936 for (
int i = 0; i < mipmapLevel; i++) {
937 width = Math.max(width / 2, 1);
938 height = Math.max(height / 2, 1);
940 dataWidth = Math.max(dataWidth / 2, 1);
941 dataHeight = Math.max(dataHeight / 2, 1);
944 buffer = data.getMipmapData()[mipmapLevel];
967 if (srcx + width > dataWidth) {
968 width = dataWidth - srcx;
970 if (srcy + height > dataHeight) {
971 height = dataHeight - srcy;
973 if (dstx + width > texWidth) {
974 width = texWidth - dstx;
976 if (dsty + height > texHeight) {
977 height = texHeight - dsty;
980 checkCompressedTextureExtensions(gl, data);
982 if (data.isDataCompressed()) {
983 gl.glCompressedTexSubImage2D(newTarget, mipmapLevel,
984 dstx, dsty, width, height,
985 data.getInternalFormat(),
986 buffer.remaining(), buffer);
988 final int[] align = { 0 };
989 final int[] rowLength = { 0 };
990 final int[] skipRows = { 0 };
991 final int[] skipPixels = { 0 };
992 gl.glGetIntegerv(
GL.GL_UNPACK_ALIGNMENT, align, 0);
994 gl.glGetIntegerv(GL2ES2.GL_UNPACK_ROW_LENGTH, rowLength, 0);
995 gl.glGetIntegerv(GL2ES2.GL_UNPACK_SKIP_ROWS, skipRows, 0);
996 gl.glGetIntegerv(GL2ES2.GL_UNPACK_SKIP_PIXELS, skipPixels, 0);
998 gl.glPixelStorei(
GL.GL_UNPACK_ALIGNMENT, data.getAlignment());
999 if (DEBUG && VERBOSE) {
1000 System.out.println(
"Row length = " + rowlen);
1001 System.out.println(
"skip pixels = " + srcx);
1002 System.out.println(
"skip rows = " + srcy);
1003 System.out.println(
"dstx = " + dstx);
1004 System.out.println(
"dsty = " + dsty);
1005 System.out.println(
"width = " + width);
1006 System.out.println(
"height = " + height);
1009 gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, rowlen);
1010 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_ROWS, srcy);
1011 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_PIXELS, srcx);
1013 if ( rowlen!=0 && rowlen!=width &&
1014 srcy!=0 && srcx!=0 ) {
1015 throw new GLException(
"rowlen and/or x/y offset only available for GL2");
1019 gl.glTexSubImage2D(newTarget, mipmapLevel,
1020 dstx, dsty, width, height,
1021 data.getPixelFormat(), data.getPixelType(),
1023 gl.glPixelStorei(
GL.GL_UNPACK_ALIGNMENT, align[0]);
1025 gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, rowLength[0]);
1026 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_ROWS, skipRows[0]);
1027 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_PIXELS, skipPixels[0]);
1032 void checkCompressedTextureExtensions(
const GL& gl,
const TextureData& data) {
1033 if (data.isDataCompressed()) {
1034 switch (data.getInternalFormat()) {
1035 case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
1036 case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
1037 case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
1038 case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
1039 if (!gl.isExtensionAvailable(GLExtensions.EXT_texture_compression_s3tc) &&
1040 !gl.isExtensionAvailable(GLExtensions.NV_texture_compression_vtc)) {
1041 throw new GLException(
"DXTn compressed textures not supported by this graphics card");
1052 bool validateTexID(
const GL& gl,
bool throwException) {
1053 if( 0 == m_texID ) {
1054 if( m_ownsTextureID ) {
1055 ::glGenTextures(1, &m_texID);
1056 if ( 0 == m_texID && throwException ) {
1057 throw RenderException(
"Create texture ID invalid: texID "+std::to_string(texID)+
", glerr "+jau::to_hexstring(::glGetError()),
E_FILE_LINE);
1059 }
else if ( throwException ) {
1060 if( !ownsTextureID ) {
1061 throw RenderException(
"Invalid external texture ID",
E_FILE_LINE;
1063 throw RenderException(
"No GL context given, can't create texture ID",
E_FILE_LINE);
1067 return 0 != m_texID;
1071 static bool haveNPOT(
const GL& gl) {
1072 return !disableNPOT && gl.isNPOTTextureAvailable();
1075 static bool haveTexRect(
const GL& gl) {
1076 return (!disableTexRect &&
1077 TextureIO.isTexRectEnabled() &&
1078 gl.isExtensionAvailable(GLExtensions.ARB_texture_rectangle));
1081 private static boolean preferTexRect(
final GL gl) {
1085 if (NativeWindowFactory.TYPE_MACOSX == NativeWindowFactory.getNativeWindowType(
false)) {
1086 final String vendor = gl.glGetString(
GL.GL_VENDOR);
1087 if (vendor != null && vendor.startsWith(
"ATI")) {
Class holding OpenGL extension strings, commonly used by Gamp's implementation.
static constexpr std::string_view SGIS_generate_mipmap
static constexpr std::string_view VERSION_1_4
static constexpr std::string_view EXT_abgr
static constexpr std::string_view VERSION_1_2
Rectangular texture coordinates.
constexpr size_t getEstimatedMemorySize() const noexcept
Returns an estimate of the amount of texture memory in bytes this Texture consumes.
constexpr GLenum target() const noexcept
Returns the OpenGL "target" of this texture.
std::string toString() const noexcept
Texture(GLenum target)
Constructor for use when creating e.g.
constexpr float aspectRatio() const noexcept
Returns the original aspect ratio of the image, defined as (image width) / (image height),...
TextureCoords getSubImageTexCoords(const jau::math::Point2i &bl, const jau::math::Point2i &tr) const noexcept
Returns the set of texture coordinates corresponding to the specified sub-image.
void setTexParameterf(const GL &gl, GLenum parameterName, GLfloat value)
Sets the OpenGL floating-point texture parameter for the texture's target.
Texture(GLuint textureID, bool ownsTextureID, GLenum target, const Point2u32 &texSize, const Point2u32 &imgSize, bool mustFlipVertically)
Constructor to wrap an OpenGL texture ID from an external library and allows some of the base methods...
Texture(GL &gl, const TextureData &data)
constexpr const Point2u32 & texSize() const noexcept
Returns the dimension of the allocated OpenGL texture in pixels.
void updateImage(const GL &gl, const TextureData &data)
Updates the entire content area incl.
constexpr bool mustFlipVertically() const noexcept
Indicates whether this texture's texture coordinates must be flipped vertically in order to properly ...
void updateSubImage(const GL &gl, const TextureData &data, int mipmapLevel, int dstx, int dsty, int srcx, int srcy, int width, int height)
Updates a subregion of the content area of this texture using the specified sub-region of the given d...
bool isUsingAutoMipmapGeneration()
Indicates whether this Texture is using automatic mipmap generation (via the OpenGL texture parameter...
void enable(const GL &gl) const noexcept
Enables this texture's target (e.g., GL_TEXTURE_2D) in the given GL context's state.
void setTexParameteriv(const GL &gl, GLenum parameterName, GLint params[])
Sets the OpenGL multi-integer texture parameter for the texture's target.
constexpr GLenum imageTarget() const noexcept
Returns the image OpenGL "target" of this texture, or its sub-components if cubemap.
void destroy(const GL &) noexcept
Destroys and nulls the underlying native texture used by this Texture instance if owned,...
void setMustFlipVertically(bool v) noexcept
Change whether the TextureData requires a vertical flip of the texture coords.
constexpr GLuint getTextureObject()
Returns the underlying OpenGL texture object for this texture, maybe 0 if not yet generated.
constexpr bool ownsTexture()
Returns whether getTextureObject() is owned by this Texture instance.
void setTexParameteri(const GL &gl, GLenum parameterName, GLint value)
Sets the OpenGL integer texture parameter for the texture's target.
void updateImage(const GL &gl, const TextureData &data, int targetOverride)
Updates the content area incl.
void disable(const GL &gl) const noexcept
Disables this texture's target (e.g., GL_TEXTURE_2D) in the given GL state.
void setTexParameterfv(const GL &gl, GLenum parameterName, GLfloat params[])
Sets the OpenGL multi-floating-point texture parameter for the texture's target.
const TextureCoords & imageTexCoords() const noexcept
Returns the set of texture coordinates corresponding to the entire image.
constexpr const Point2u32 & imgSize() const noexcept
Returns the dimension of the image contained within this texture.
void set(const Point2u32 &texSize, const Point2u32 &imgSize)
Pending setup or update of texture and image dimensions.
void bind(const GL &gl) noexcept
Binds this texture to the given GL context.
void updateSubImage(const GL &gl, const TextureData &data, int mipmapLevel, int x, int y)
Updates a subregion of the content area of this texture using the given data.
GLuint getTextureObject(const GL &gl) noexcept
Returns the underlying OpenGL texture object for this texture and generates it if not done yet.
constexpr T bit_ceil(const T n) noexcept
If the given n is not is_power_of_2() return next_power_of_2(), otherwise return n unchanged.
constexpr bool is_power_of_2(const T x) noexcept
Power of 2 test in O(1), i.e.
Point2I< uint32_t > Point2u32
std::string toHexString(const void *data, const nsize_t length, const lb_endian_t byteOrder=lb_endian_t::big, const LoUpCase capitalization=LoUpCase::lower, const PrefixOpt prefix=PrefixOpt::prefix) noexcept
Produce a hexadecimal string representation of the given lsb-first byte values.
std::string to_string(const bit_order_t v) noexcept
Return std::string representation of the given bit_order_t.
Represents the data for an OpenGL texture.
TextureData(const GLProfile &glp, GLenum internalFormat, Point2u32 &size, unsigned border, GLenum pixelFormat, GLenum pixelType, bool mipmap, bool dataIsCompressed, bool mustFlipVertically, bytes_t &buffer)
Constructs a new TextureData object with the specified parameters and data contained in the given Buf...