diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 113a7617e2..2a4c5e0493 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -85,7 +85,7 @@ void AsyncImageFileView::SetFilename(std::string filename) { void AsyncImageFileView::Draw(UIContext &dc) { using namespace Draw; if (!texture_ && !textureFailed_ && !filename_.empty()) { - texture_ = CreateTextureFromFile(dc.GetDrawContext(), filename_.c_str(), DETECT); + texture_ = CreateTextureFromFile(dc.GetDrawContext(), filename_.c_str(), DETECT, true); if (!texture_) textureFailed_ = true; } diff --git a/UI/TextureUtil.cpp b/UI/TextureUtil.cpp index 6eb4a791f7..d2b8395cf8 100644 --- a/UI/TextureUtil.cpp +++ b/UI/TextureUtil.cpp @@ -1,6 +1,9 @@ +#include + #include "thin3d/thin3d.h" #include "image/zim_load.h" #include "image/png_load.h" +#include "math/math_util.h" #include "file/vfs.h" #include "ext/jpge/jpgd.h" #include "UI/TextureUtil.h" @@ -83,7 +86,7 @@ static bool LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType ty return *num_levels > 0; } -bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type) { +bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips) { using namespace Draw; int width[16]{}, height[16]{}; @@ -110,13 +113,16 @@ bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, Imag texture_ = nullptr; } + int potentialLevels = std::min(log2i(width[0]), log2i(height[0])); + TextureDesc desc{}; desc.type = TextureType::LINEAR2D; desc.format = fmt; desc.width = width[0]; desc.height = height[0]; desc.depth = 1; - desc.mipLevels = num_levels; + desc.mipLevels = generateMips ? potentialLevels : num_levels; + desc.generateMips = generateMips && potentialLevels > num_levels; for (int i = 0; i < num_levels; i++) { desc.initData.push_back(image[i]); } @@ -125,10 +131,11 @@ bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, Imag if (image[i]) free(image[i]); } + return true; } -bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type) { +bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { filename_ = ""; size_t fileSize; uint8_t *buffer = VFSReadFile(filename.c_str(), &fileSize); @@ -136,7 +143,7 @@ bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType typ ELOG("Failed to read file %s", filename.c_str()); return false; } - bool retval = LoadFromFileData(buffer, fileSize, type); + bool retval = LoadFromFileData(buffer, fileSize, type, generateMips); if (retval) { filename_ = filename; } else { @@ -146,11 +153,11 @@ bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType typ return retval; } -ManagedTexture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type) { +ManagedTexture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips) { if (!draw) return nullptr; ManagedTexture *mtex = new ManagedTexture(draw); - if (!mtex->LoadFromFile(filename, type)) { + if (!mtex->LoadFromFile(filename, type, generateMips)) { delete mtex; return nullptr; } @@ -158,10 +165,10 @@ ManagedTexture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filen } // TODO: Remove the code duplication between this and LoadFromFileData -ManagedTexture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType type) { +ManagedTexture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType type, bool generateMips) { if (!draw) return nullptr; ManagedTexture *mtex = new ManagedTexture(draw); - mtex->LoadFromFileData(data, size, type); + mtex->LoadFromFileData(data, size, type, generateMips); return mtex; } \ No newline at end of file diff --git a/UI/TextureUtil.h b/UI/TextureUtil.h index 1b741af289..eaa5b14a20 100644 --- a/UI/TextureUtil.h +++ b/UI/TextureUtil.h @@ -42,8 +42,8 @@ public: } } - bool LoadFromFile(const std::string &filename, ImageFileType type = ImageFileType::DETECT); - bool LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type = ImageFileType::DETECT); + bool LoadFromFile(const std::string &filename, ImageFileType type = ImageFileType::DETECT, bool generateMips = false); + bool LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type = ImageFileType::DETECT, bool generateMips = false); Draw::Texture *GetTexture() { return texture_; } // For immediate use, don't store. int Width() const { return texture_->Width(); } int Height() const { return texture_->Height(); } @@ -54,6 +54,5 @@ private: std::string filename_; // Textures that are loaded from files can reload themselves automatically. }; -// Common Thin3D function, uses CreateTexture -ManagedTexture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType fileType); -ManagedTexture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType fileType); +ManagedTexture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType fileType, bool generateMips = false); +ManagedTexture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType fileType, bool generateMips = false); diff --git a/ext/native/image/zim_load.cpp b/ext/native/image/zim_load.cpp index 52e4152f80..8046db06bb 100644 --- a/ext/native/image/zim_load.cpp +++ b/ext/native/image/zim_load.cpp @@ -5,6 +5,7 @@ #include "base/logging.h" #include "zlib.h" #include "image/zim_load.h" +#include "math/math_util.h" #include "file/vfs.h" int ezuncompress(unsigned char* pDest, long* pnDestLen, const unsigned char* pSrc, long nSrcLen) { @@ -46,14 +47,6 @@ int ezuncompress(unsigned char* pDest, long* pnDestLen, const unsigned char* pSr return nExtraChunks ? Z_BUF_ERROR : Z_OK; } -static unsigned int log2i(unsigned int val) { - unsigned int ret = -1; - while (val != 0) { - val >>= 1; ret++; - } - return ret; -} - int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int *flags, uint8_t **image) { if (zim[0] != 'Z' || zim[1] != 'I' || zim[2] != 'M' || zim[3] != 'G') { ELOG("Not a ZIM file"); diff --git a/ext/native/math/math_util.h b/ext/native/math/math_util.h index d4a299b0bd..b06fed5143 100644 --- a/ext/native/math/math_util.h +++ b/ext/native/math/math_util.h @@ -1,5 +1,8 @@ #pragma once +// Some of the stuff in this file are snippets from all over the web, esp. dspmusic.org. I think it's all public domain. +// In any case, very little of it is used anywhere at the moment. + #include #include @@ -36,14 +39,19 @@ inline uint32_t RoundUpToPowerOf2(uint32_t v) { return v; } +inline uint32_t log2i(uint32_t val) { + unsigned int ret = -1; + while (val != 0) { + val >>= 1; ret++; + } + return ret; +} + #define PI 3.141592653589793f #ifndef M_PI #define M_PI 3.141592653589793f #endif -// The stuff in this file is from all over the web, esp. dspmusic.org. I think it's all public domain. -// In any case, very little of it is used anywhere at the moment. - // Calculate pseudo-random 32 bit number based on linear congruential method. void SetSeed(unsigned int seed); unsigned int GenerateRandomNumber(); diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 338ed9b889..7e928ee41c 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -299,6 +299,7 @@ enum FormatSupport { FMT_TEXTURE = 2, FMT_INPUTLAYOUT = 4, FMT_DEPTHSTENCIL = 8, + FMT_AUTOGEN_MIPS = 16, }; enum InfoField { @@ -552,6 +553,7 @@ struct TextureDesc { int height; int depth; int mipLevels; + bool generateMips; std::vector initData; }; diff --git a/ext/native/thin3d/thin3d_d3d11.cpp b/ext/native/thin3d/thin3d_d3d11.cpp index 435d6aa010..1cefc31586 100644 --- a/ext/native/thin3d/thin3d_d3d11.cpp +++ b/ext/native/thin3d/thin3d_d3d11.cpp @@ -669,6 +669,12 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { return false; } + bool generateMips = desc.generateMips; + if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) { + // D3D11 does not support autogenerating mipmaps for this format. + generateMips = false; + } + D3D11_TEXTURE2D_DESC descColor{}; descColor.Width = desc.width; descColor.Height = desc.height; @@ -678,12 +684,12 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { descColor.SampleDesc.Count = 1; descColor.SampleDesc.Quality = 0; descColor.Usage = D3D11_USAGE_DEFAULT; - descColor.BindFlags = D3D11_BIND_SHADER_RESOURCE; + descColor.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE; + descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0; descColor.CPUAccessFlags = 0; - descColor.MiscFlags = 0; D3D11_SUBRESOURCE_DATA initData[12]{}; - if (desc.initData.size()) { + if (desc.initData.size() && !generateMips) { int w = desc.width; int h = desc.height; for (int i = 0; i < (int)desc.initData.size(); i++) { @@ -695,7 +701,7 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { } } - HRESULT hr = device_->CreateTexture2D(&descColor, desc.initData.size() ? initData : nullptr, &tex->tex); + HRESULT hr = device_->CreateTexture2D(&descColor, (desc.initData.size() && !generateMips) ? initData : nullptr, &tex->tex); if (!SUCCEEDED(hr)) { delete tex; return nullptr; @@ -705,6 +711,10 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { delete tex; return nullptr; } + if (generateMips && desc.initData.size() >= 1) { + context_->UpdateSubresource(tex->tex, 0, nullptr, desc.initData[0], desc.width * (int)DataFormatSizeInBytes(desc.format), 0); + context_->GenerateMips(tex->view); + } return tex; } @@ -1062,6 +1072,8 @@ uint32_t D3D11DrawContext::GetDataFormatSupport(DataFormat fmt) const { support |= FMT_INPUTLAYOUT; if (giSupport & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL) support |= FMT_DEPTHSTENCIL; + if (giSupport & D3D11_FORMAT_SUPPORT_MIP_AUTOGEN) + support |= FMT_AUTOGEN_MIPS; return support; } diff --git a/ext/native/thin3d/thin3d_d3d9.cpp b/ext/native/thin3d/thin3d_d3d9.cpp index 96704a1362..71b3ee6c58 100644 --- a/ext/native/thin3d/thin3d_d3d9.cpp +++ b/ext/native/thin3d/thin3d_d3d9.cpp @@ -345,10 +345,12 @@ bool D3D9Texture::Create(const TextureDesc &desc) { pool = D3DPOOL_DEFAULT; usage = D3DUSAGE_DYNAMIC; } + if (desc.generateMips) + usage |= D3DUSAGE_AUTOGENMIPMAP; switch (type_) { case TextureType::LINEAR1D: case TextureType::LINEAR2D: - hr = device_->CreateTexture(desc.width, desc.height, desc.mipLevels, usage, d3dfmt_, pool, &tex_, NULL); + hr = device_->CreateTexture(desc.width, desc.height, desc.generateMips ? 0 : desc.mipLevels, usage, d3dfmt_, pool, &tex_, NULL); break; case TextureType::LINEAR3D: hr = device_->CreateVolumeTexture(desc.width, desc.height, desc.depth, desc.mipLevels, usage, d3dfmt_, pool, &volTex_, NULL); @@ -363,7 +365,10 @@ bool D3D9Texture::Create(const TextureDesc &desc) { } if (desc.initData.size()) { - for (int i = 0; i < (int)desc.initData.size(); i++) { + // In D3D9, after setting D3DUSAGE_AUTOGENMIPS, we can only access the top layer. The rest will be + // automatically generated. + int maxLevel = desc.generateMips ? 1 : (int)desc.initData.size(); + for (int i = 0; i < maxLevel; i++) { SetImageData(0, 0, 0, width_, height_, depth_, i, 0, desc.initData[i]); } } @@ -1154,7 +1159,7 @@ DrawContext *T3DCreateDX9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapt uint32_t D3D9Context::GetDataFormatSupport(DataFormat fmt) const { switch (fmt) { case DataFormat::B8G8R8A8_UNORM: - return FMT_RENDERTARGET | FMT_TEXTURE; + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_AUTOGEN_MIPS; case DataFormat::R4G4B4A4_UNORM_PACK16: return 0; @@ -1163,10 +1168,10 @@ uint32_t D3D9Context::GetDataFormatSupport(DataFormat fmt) const { case DataFormat::R5G6B5_UNORM_PACK16: case DataFormat::A1R5G5B5_UNORM_PACK16: case DataFormat::A4R4G4B4_UNORM_PACK16: - return FMT_RENDERTARGET | FMT_TEXTURE; // native support + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_AUTOGEN_MIPS; // native support case DataFormat::R8G8B8A8_UNORM: - return FMT_RENDERTARGET | FMT_TEXTURE | FMT_INPUTLAYOUT; + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_INPUTLAYOUT | FMT_AUTOGEN_MIPS; case DataFormat::R32_FLOAT: case DataFormat::R32G32_FLOAT: diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index c5e363a433..c42b3f71b8 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -683,19 +683,19 @@ OpenGLTexture::OpenGLTexture(const TextureDesc &desc) { height_ = (height_ + 1) / 2; level++; } - mipLevels_ = level; + mipLevels_ = desc.generateMips ? desc.mipLevels : level; #ifdef USING_GLES2 if (gl_extensions.GLES3) { - glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, level - 1); + glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, mipLevels_ - 1); } #else - glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, level - 1); + glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, mipLevels_ - 1); #endif - glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, level > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); + glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, mipLevels_ > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if ((int)desc.initData.size() < desc.mipLevels) { + if ((int)desc.initData.size() < desc.mipLevels && desc.generateMips) { ILOG("Generating mipmaps"); AutoGenMipmaps(); } @@ -1667,9 +1667,9 @@ void OpenGLContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const { switch (fmt) { case DataFormat::B8G8R8A8_UNORM: - return FMT_RENDERTARGET | FMT_TEXTURE; + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_AUTOGEN_MIPS; case DataFormat::B4G4R4A4_UNORM_PACK16: - return FMT_RENDERTARGET | FMT_TEXTURE; // native support + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_AUTOGEN_MIPS; // native support case DataFormat::A4R4G4B4_UNORM_PACK16: #ifndef USING_GLES2 // Can support this if _REV formats are supported. @@ -1678,7 +1678,7 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const { return 0; case DataFormat::R8G8B8A8_UNORM: - return FMT_RENDERTARGET | FMT_TEXTURE | FMT_INPUTLAYOUT; + return FMT_RENDERTARGET | FMT_TEXTURE | FMT_INPUTLAYOUT | FMT_AUTOGEN_MIPS; case DataFormat::R32_FLOAT: case DataFormat::R32G32_FLOAT: