diff --git a/examples/models/models_skybox.c b/examples/models/models_skybox.c index fc9627b42..4689acd77 100644 --- a/examples/models/models_skybox.c +++ b/examples/models/models_skybox.c @@ -191,7 +191,7 @@ static TextureCubemap GenTextureCubemap(Shader shader, Texture2D panorama, int s // STEP 1: Setup framebuffer //------------------------------------------------------------------------------------------ unsigned int rbo = rlLoadTextureDepth(size, size, true); - cubemap.id = rlLoadTextureCubemap(0, size, format); + cubemap.id = rlLoadTextureCubemap(0, size, format, 1); unsigned int fbo = rlLoadFramebuffer(); rlFramebufferAttach(fbo, rbo, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); diff --git a/src/rlgl.h b/src/rlgl.h index 3d6a00628..56f01e5f4 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -749,7 +749,7 @@ RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void // Textures management RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format); // Load texture cubemap data +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format @@ -3386,11 +3386,17 @@ unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) // Load texture cubemap // NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), // expected the following convention: +X, -X, +Y, -Y, +Z, -Z -unsigned int rlLoadTextureCubemap(const void *data, int size, int format) +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) { unsigned int id = 0; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + unsigned int dataSize = rlGetPixelDataSize(size, size, format); glGenTextures(1, &id); @@ -3401,9 +3407,12 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format) if (glInternalFormat != 0) { - // Load cubemap faces - for (unsigned int i = 0; i < 6; i++) + // Load cubemap faces/mipmaps + for (unsigned int i = 0; i < 6 * mipmapCount; i++) { + int mipmapLevel = i / 6; + int face = i % 6; + if (data == NULL) { if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) @@ -3411,14 +3420,14 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format) if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); - else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); } else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); } else { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); } #if defined(GRAPHICS_API_OPENGL_33) @@ -3437,11 +3446,25 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format) glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } #endif + if (face == 5) { + mipSize /= 2; + if (data != NULL) + dataPtr += dataSize * 6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } } } // Set cubemap texture sampling parameters - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (mipmapCount > 1) { + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); diff --git a/src/rtextures.c b/src/rtextures.c index ca3ad0990..d0f9d4a9e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -2390,22 +2390,16 @@ void ImageMipmaps(Image *image) else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated"); // Pointer to allocated memory point where store next mipmap level data - unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format); + unsigned char *nextmip = image->data; - mipWidth = image->width/2; - mipHeight = image->height/2; + mipWidth = image->width; + mipHeight = image->height; mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); Image imCopy = ImageCopy(*image); for (int i = 1; i < mipCount; i++) { - TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); - - ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter - - memcpy(nextmip, imCopy.data, mipSize); nextmip += mipSize; - image->mipmaps++; mipWidth /= 2; mipHeight /= 2; @@ -2415,9 +2409,20 @@ void ImageMipmaps(Image *image) if (mipHeight < 1) mipHeight = 1; mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + + if (i < image->mipmaps) + continue; + + TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + + memcpy(nextmip, imCopy.data, mipSize); } UnloadImage(imCopy); + + image->mipmaps = mipCount; } else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available"); } @@ -3906,7 +3911,6 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); else { @@ -4019,6 +4023,34 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color } if (useSrcMod) UnloadImage(srcMod); // Unload source modified image + + if (dst->mipmaps > 1 && src.mipmaps > 1) { + Image mipmapDst = *dst; + mipmapDst.data = (char *) mipmapDst.data + GetPixelDataSize(mipmapDst.width, mipmapDst.height, mipmapDst.format); + mipmapDst.width /= 2; + mipmapDst.height /= 2; + mipmapDst.mipmaps--; + + Image mipmapSrc = src; + mipmapSrc.data = (char *) mipmapSrc.data + GetPixelDataSize(mipmapSrc.width, mipmapSrc.height, mipmapSrc.format); + mipmapSrc.width /= 2; + mipmapSrc.height /= 2; + mipmapSrc.mipmaps--; + + Rectangle mipmapSrcRec = srcRec; + mipmapSrcRec.width /= 2; + mipmapSrcRec.height /= 2; + mipmapSrcRec.x /= 2; + mipmapSrcRec.y /= 2; + + Rectangle mipmapDstRec = dstRec; + mipmapDstRec.width /= 2; + mipmapDstRec.height /= 2; + mipmapDstRec.x /= 2; + mipmapDstRec.y /= 2; + + ImageDraw(&mipmapDst, mipmapSrc, mipmapSrcRec, mipmapDstRec, tint); + } } } @@ -4162,6 +4194,9 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) faces = GenImageColor(size, size*6, MAGENTA); ImageFormat(&faces, image.format); + ImageMipmaps(&image); + ImageMipmaps(&faces); + // NOTE: Image formatting does not work with compressed textures for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); @@ -4169,7 +4204,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) // NOTE: Cubemap data is expected to be provided as 6 images in a single data array, // one after the other (that's a vertical image), following convention: +X, -X, +Y, -Y, +Z, -Z - cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format, faces.mipmaps); if (cubemap.id != 0) {