Rebase TextureImporting onto cmake branch

This commit is contained in:
Jack Andersen 2019-06-03 14:47:30 -10:00
parent b507196851
commit d969c0d053
38 changed files with 1309 additions and 1538 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "externals/LibCommon"]
path = externals/LibCommon
url = ../LibCommon.git
[submodule "externals/stb"]
path = externals/stb
url = https://github.com/nothings/stb

View File

@ -39,5 +39,7 @@ set(CODEGEN_GENERATE_INSTALL_TARGETS OFF)
set(CODEGEN_BUILD_PACKAGE_DURING_CONFIGURE ON)
add_subdirectory(externals/LibCommon)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/externals/stb)
add_subdirectory(src/Core)
add_subdirectory(src/Editor)

1
externals/stb vendored Submodule

@ -0,0 +1 @@
Subproject commit 1034f5e5c4809ea0a7f4387e0cd37c5184de3cdd

View File

@ -4,7 +4,7 @@
#include "CResourceStore.h"
#include "CVirtualDirectory.h"
#include "Core/Resource/CResTypeInfo.h"
#include "Core/Resource/EResType.h"
#include "Core/Resource/EResourceType.h"
#include <Common/CAssetID.h>
#include <Common/CFourCC.h>
#include <Common/Flags.h>

View File

@ -2,7 +2,7 @@
#define CRESOURCESTORE_H
#include "CVirtualDirectory.h"
#include "Core/Resource/EResType.h"
#include "Core/Resource/EResourceType.h"
#include <Common/CAssetID.h>
#include <Common/CFourCC.h>
#include <Common/FileUtil.h>

View File

@ -2,7 +2,7 @@
#define CVIRTUALDIRECTORY
/* Virtual directory system used to look up resources by their location in the filesystem. */
#include "Core/Resource/EResType.h"
#include "Core/Resource/EResourceType.h"
#include <Common/Macros.h>
#include <Common/TString.h>
#include <vector>

View File

@ -1,7 +1,7 @@
#ifndef NCORETESTS_H
#define NCORETESTS_H
#include "Core/Resource/EResType.h"
#include "Core/Resource/EResourceType.h"
/** Unit tests for Core */
namespace NCoreTests

View File

@ -46,9 +46,10 @@ void CFramebuffer::Init()
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
mpRenderbuffer = new CRenderbuffer(mWidth, mHeight);
mpTexture = new CTexture(mWidth, mHeight);
mpRenderbuffer->SetMultisamplingEnabled(mEnableMultisampling);
mpTexture = new CTexture(mWidth, mHeight);
mpTexture->SetMultisamplingEnabled(mEnableMultisampling);
mpTexture->CreateRenderResources();
InitBuffers();
mInitialized = true;
}
@ -101,9 +102,9 @@ void CFramebuffer::InitBuffers()
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mpRenderbuffer->BufferID()
);
mpTexture->Bind(0);
mpTexture->BindToSampler(0);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D), mpTexture->TextureID(), 0
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D), mpTexture->RenderResource(), 0
);
mStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);

View File

@ -2,7 +2,7 @@
#define CFRAMEBUFFER_H
#include "CRenderbuffer.h"
#include "Core/Resource/CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include <GL/glew.h>
class CFramebuffer

View File

@ -237,7 +237,7 @@ void CDrawUtil::DrawBillboard(CTexture* pTexture, const CVector3f& Position, con
static GLuint TintLoc = mpBillboardShader->GetUniformLocation("TintColor");
glUniform4f(TintLoc, Tint.R, Tint.G, Tint.B, Tint.A);
pTexture->Bind(0);
pTexture->BindToSampler(0);
// Set other properties
CMaterial::KillCachedMaterial();
@ -271,8 +271,8 @@ void CDrawUtil::DrawLightBillboard(ELightType Type, const CColor& LightColor, co
CTexture *pTexA = GetLightTexture(Type);
CTexture *pTexB = GetLightMask(Type);
pTexA->Bind(0);
pTexB->Bind(1);
pTexA->BindToSampler(0);
pTexB->BindToSampler(1);
static GLuint TextureLoc = mpLightBillboardShader->GetUniformLocation("Texture");
static GLuint MaskLoc = mpLightBillboardShader->GetUniformLocation("LightMask");
@ -360,7 +360,7 @@ CShader* CDrawUtil::GetTextShader()
void CDrawUtil::LoadCheckerboardTexture(uint32 GLTextureUnit)
{
Init();
mpCheckerTexture->Bind(GLTextureUnit);
mpCheckerTexture->BindToSampler(GLTextureUnit);
}
CTexture* CDrawUtil::GetLightTexture(ELightType Type)

View File

@ -188,7 +188,7 @@ void CRenderer::RenderBloom()
CDrawUtil::UseTextureShader();
glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
mPostProcessFramebuffer.Texture()->Bind(0);
mPostProcessFramebuffer.Texture()->BindToSampler(0);
CDrawUtil::DrawSquare();
// Pass 2: Horizontal blur
@ -197,7 +197,7 @@ void CRenderer::RenderBloom()
CDrawUtil::UseTextureShader(CColor::skGray);
glBlendFunc(GL_ONE, GL_ZERO);
mBloomFramebuffers[0].Texture()->Bind(0);
mBloomFramebuffers[0].Texture()->BindToSampler(0);
CDrawUtil::DrawSquare();
for (uint32 iPass = 0; iPass < 6; iPass++)
@ -217,7 +217,7 @@ void CRenderer::RenderBloom()
CDrawUtil::UseTextureShader(CColor::skGray);
glBlendFunc(GL_ONE, GL_ZERO);
mBloomFramebuffers[1].Texture()->Bind(0);
mBloomFramebuffers[1].Texture()->BindToSampler(0);
CDrawUtil::DrawSquare();
for (uint32 iPass = 0; iPass < 6; iPass++)
@ -240,7 +240,7 @@ void CRenderer::RenderBloom()
CDrawUtil::UseTextureShader();
glBlendFunc(GL_ONE, GL_ONE);
mBloomFramebuffers[2].Texture()->Bind(0);
mBloomFramebuffers[2].Texture()->BindToSampler(0);
CDrawUtil::DrawSquare();
if (mBloomMode == EBloomMode::BloomMaps)

View File

@ -12,7 +12,7 @@
#include "Core/OpenGL/CFramebuffer.h"
#include "Core/Resource/CFont.h"
#include "Core/Resource/CLight.h"
#include "Core/Resource/CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include "Core/Scene/CSceneNode.h"
#include <Common/CColor.h>

View File

@ -42,7 +42,7 @@ CVector2f CFont::RenderString(const TString& rkString, CRenderer* /*pRenderer*/,
GLuint ModelMtxLoc = pTextShader->GetUniformLocation("ModelMtx");
GLuint ColorLoc = pTextShader->GetUniformLocation("FontColor");
GLuint LayerLoc = pTextShader->GetUniformLocation("RGBALayer");
mpFontTexture->Bind(0);
mpFontTexture->BindToSampler(0);
smGlyphVertices->Bind();
glDisable(GL_DEPTH_TEST);

View File

@ -2,7 +2,7 @@
#define CFONT_H
#include "CResource.h"
#include "CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include "TResPtr.h"
#include "Core/Resource/Model/CVertex.h"
#include "Core/OpenGL/CDynamicVertexBuffer.h"

View File

@ -2,7 +2,7 @@
#define MATERIAL_H
#include "CMaterialPass.h"
#include "CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include "TResPtr.h"
#include "Core/Resource/Model/EVertexAttribute.h"
#include "Core/Render/FRenderOptions.h"

View File

@ -81,7 +81,7 @@ void CMaterialPass::HashParameters(CFNV1A& rHash)
void CMaterialPass::LoadTexture(uint32 PassIndex)
{
if (mpTexture)
mpTexture->Bind(PassIndex);
mpTexture->BindToSampler(PassIndex);
}
void CMaterialPass::SetAnimCurrent(FRenderOptions Options, uint32 PassIndex)

View File

@ -2,7 +2,7 @@
#define CMATERIALPASS_H
#include "TResPtr.h"
#include "CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include "ETevEnums.h"
#include "Core/Render/FRenderOptions.h"
#include <Common/CFourCC.h>

View File

@ -2,7 +2,7 @@
#define CMATERIALSET_H
#include "CMaterial.h"
#include "CTexture.h"
#include "Core/Resource/Texture/CTexture.h"
#include <Common/EGame.h>
#include <Common/FileIO/IInputStream.h>

View File

@ -1,7 +1,7 @@
#ifndef CRESTYPEFILTER_H
#define CRESTYPEFILTER_H
#include "EResType.h"
#include "EResourceType.h"
#include "CResTypeInfo.h"
#include "Core/GameProject/CResourceEntry.h"

View File

@ -1,7 +1,7 @@
#ifndef CRESTYPEINFO
#define CRESTYPEINFO
#include "EResType.h"
#include "EResourceType.h"
#include <Common/CFourCC.h>
#include <Common/EGame.h>
#include <Common/Flags.h>

View File

@ -2,7 +2,7 @@
#define CRESOURCE_H
#include "CResTypeInfo.h"
#include "EResType.h"
#include "EResourceType.h"
#include "Core/GameProject/CDependencyTree.h"
#include "Core/GameProject/CResourceEntry.h"
#include "Core/GameProject/CResourceStore.h"

View File

@ -1,372 +0,0 @@
#include "CTexture.h"
#include <cmath>
CTexture::CTexture(CResourceEntry *pEntry /*= 0*/)
: CResource(pEntry)
, mTexelFormat(ETexelFormat::RGBA8)
, mSourceTexelFormat(ETexelFormat::RGBA8)
, mWidth(0)
, mHeight(0)
, mNumMipMaps(0)
, mLinearSize(0)
, mEnableMultisampling(false)
, mBufferExists(false)
, mpImgDataBuffer(nullptr)
, mImgDataSize(0)
, mGLBufferExists(false)
{
}
CTexture::CTexture(uint32 Width, uint32 Height)
: mTexelFormat(ETexelFormat::RGBA8)
, mSourceTexelFormat(ETexelFormat::RGBA8)
, mWidth((uint16) Width)
, mHeight((uint16) Height)
, mNumMipMaps(1)
, mLinearSize(Width * Height * 4)
, mEnableMultisampling(false)
, mBufferExists(false)
, mpImgDataBuffer(nullptr)
, mImgDataSize(0)
, mGLBufferExists(false)
{
}
CTexture::~CTexture()
{
DeleteBuffers();
}
bool CTexture::BufferGL()
{
GLenum BindTarget = (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D);
glGenTextures(1, &mTextureID);
glBindTexture(BindTarget, mTextureID);
GLenum GLFormat, GLType;
bool IsCompressed = false;
switch (mTexelFormat)
{
case ETexelFormat::Luminance:
GLFormat = GL_LUMINANCE;
GLType = GL_UNSIGNED_BYTE;
break;
case ETexelFormat::LuminanceAlpha:
GLFormat = GL_LUMINANCE_ALPHA;
GLType = GL_UNSIGNED_BYTE;
break;
case ETexelFormat::RGB565:
GLFormat = GL_RGB;
GLType = GL_UNSIGNED_SHORT_5_6_5;
break;
case ETexelFormat::RGBA4:
GLFormat = GL_RGBA;
GLType = GL_UNSIGNED_SHORT_4_4_4_4;
break;
case ETexelFormat::RGBA8:
GLFormat = GL_RGBA;
GLType = GL_UNSIGNED_BYTE;
break;
case ETexelFormat::DXT1:
GLFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
IsCompressed = true;
break;
default: break;
}
// The smallest mipmaps are probably not being loaded correctly, because mipmaps in GX textures have a minimum size depending on the format, and these don't.
// Not sure specifically what accomodations should be made to fix that though so whatever.
uint32 MipSize = mLinearSize;
uint32 MipOffset = 0;
uint16 MipW = mWidth, MipH = mHeight;
for (uint32 iMip = 0; iMip < mNumMipMaps; iMip++)
{
GLvoid *pData = (mBufferExists) ? (mpImgDataBuffer + MipOffset) : NULL;
if (!IsCompressed)
{
if (mEnableMultisampling)
glTexImage2DMultisample(BindTarget, 4, GLFormat, MipW, MipH, true);
else
glTexImage2D(BindTarget, iMip, GLFormat, MipW, MipH, 0, GLFormat, GLType, pData);
}
else
glCompressedTexImage2D(BindTarget, iMip, GLFormat, MipW, MipH, 0, MipSize, pData);
MipW /= 2;
MipH /= 2;
MipOffset += MipSize;
MipSize /= 4;
}
glTexParameteri(BindTarget, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(BindTarget, GL_TEXTURE_MAX_LEVEL, mNumMipMaps - 1);
// Linear filtering on mipmaps:
glTexParameteri(BindTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(BindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Anisotropic filtering:
float MaxAnisotropy;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &MaxAnisotropy);
glTexParameterf(BindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, MaxAnisotropy);
mGLBufferExists = true;
return true;
}
void CTexture::Bind(uint32 GLTextureUnit)
{
glActiveTexture(GL_TEXTURE0 + GLTextureUnit);
if (!mGLBufferExists)
BufferGL();
GLenum BindTarget = (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D);
glBindTexture(BindTarget, mTextureID);
}
void CTexture::Resize(uint32 Width, uint32 Height)
{
if ((mWidth != Width) || (mHeight != Height))
{
DeleteBuffers();
mWidth = (uint16) Width;
mHeight = (uint16) Height;
mNumMipMaps = 1;
CalcLinearSize();
}
}
float CTexture::ReadTexelAlpha(const CVector2f& rkTexCoord)
{
// todo: support texel formats other than DXT1
// DXT1 is definitely the most complicated one anyway; try reusing CTextureDecoder functions for other formats
uint32 TexelX = (uint32) ((mWidth - 1) * rkTexCoord.X);
uint32 TexelY = (uint32) ((mHeight - 1) * (1.f - std::fmod(rkTexCoord.Y, 1.f)));
if (mTexelFormat == ETexelFormat::DXT1 && mBufferExists)
{
CMemoryInStream Buffer(mpImgDataBuffer, mImgDataSize, EEndian::SystemEndian);
// 8 bytes per 4x4 16-pixel block, left-to-right top-to-bottom
uint32 BlockIdxX = TexelX / 4;
uint32 BlockIdxY = TexelY / 4;
uint32 BlocksPerRow = mWidth / 4;
uint32 BufferPos = (8 * BlockIdxX) + (8 * BlockIdxY * BlocksPerRow);
Buffer.Seek(BufferPos, SEEK_SET);
uint16 PaletteA = Buffer.ReadShort();
uint16 PaletteB = Buffer.ReadShort();
if (PaletteA > PaletteB)
{
// No palette colors have alpha
return 1.f;
}
// We only care about alpha, which is only present on palette index 3.
// We don't need to calculate/decode the actual palette colors.
uint32 BlockCol = (TexelX & 0xF) / 4;
uint32 BlockRow = (TexelY & 0xF) / 4;
Buffer.Seek(BlockRow, SEEK_CUR);
uint8 Row = Buffer.ReadByte();
uint8 Shift = (uint8) (6 - (BlockCol * 2));
uint8 PaletteIndex = (Row >> Shift) & 0x3;
return (PaletteIndex == 3 ? 0.f : 1.f);
}
return 1.f;
}
bool CTexture::WriteDDS(IOutputStream& rOut)
{
if (!rOut.IsValid()) return false;
CopyGLBuffer();
rOut.WriteFourCC(FOURCC('DDS ')); // "DDS " fourCC
rOut.WriteLong(0x7C); // dwSize
rOut.WriteLong(0x21007); // dwFlags
rOut.WriteLong(mHeight); // dwHeight
rOut.WriteLong(mWidth); // dwWidth
rOut.WriteLong(mLinearSize); // dwPitchOrLinearSize
rOut.WriteLong(0); // dwDepth
rOut.WriteLong(mNumMipMaps - 1); // dwMipMapCount
for (uint32 iRes = 0; iRes < 11; iRes++)
rOut.WriteLong(0); // dwReserved1[11]
// DDS_PIXELFORMAT
rOut.WriteLong(32); // DDS_PIXELFORMAT.dwSize
uint32 PFFlags = 0, PFBpp = 0, PFRBitMask = 0, PFGBitMask = 0, PFBBitMask = 0, PFABitMask = 0;
switch (mTexelFormat)
{
case ETexelFormat::Luminance:
PFFlags = 0x20000;
PFBpp = 0x8;
PFRBitMask = 0xFF;
break;
case ETexelFormat::LuminanceAlpha:
PFFlags = 0x20001;
PFBpp = 0x10;
PFRBitMask = 0x00FF;
PFABitMask = 0xFF00;
break;
case ETexelFormat::RGBA4:
PFFlags = 0x41;
PFBpp = 0x10;
PFRBitMask = 0x0F00;
PFGBitMask = 0x00F0;
PFBBitMask = 0x000F;
PFABitMask = 0xF000;
break;
case ETexelFormat::RGB565:
PFFlags = 0x40;
PFBpp = 0x10;
PFRBitMask = 0xF800;
PFGBitMask = 0x7E0;
PFBBitMask = 0x1F;
break;
case ETexelFormat::RGBA8:
PFFlags = 0x41;
PFBpp = 0x20;
PFRBitMask = 0x00FF0000;
PFGBitMask = 0x0000FF00;
PFBBitMask = 0x000000FF;
PFABitMask = 0xFF000000;
break;
case ETexelFormat::DXT1:
PFFlags = 0x4;
break;
default:
break;
}
rOut.WriteLong(PFFlags); // DDS_PIXELFORMAT.dwFlags
(mTexelFormat == ETexelFormat::DXT1) ? rOut.WriteFourCC(FOURCC('DXT1')) : rOut.WriteLong(0); // DDS_PIXELFORMAT.dwFourCC
rOut.WriteLong(PFBpp); // DDS_PIXELFORMAT.dwRGBBitCount
rOut.WriteLong(PFRBitMask); // DDS_PIXELFORMAT.dwRBitMask
rOut.WriteLong(PFGBitMask); // DDS_PIXELFORMAT.dwGBitMask
rOut.WriteLong(PFBBitMask); // DDS_PIXELFORMAT.dwBBitMask
rOut.WriteLong(PFABitMask); // DDS_PIXELFORMAT.dwABitMask
rOut.WriteLong(0x401000); // dwCaps
rOut.WriteLong(0); // dwCaps2
rOut.WriteLong(0); // dwCaps3
rOut.WriteLong(0); // dwCaps4
rOut.WriteLong(0); // dwReserved2
rOut.WriteBytes(mpImgDataBuffer, mImgDataSize); // Image data
return true;
}
// ************ STATIC ************
uint32 CTexture::FormatBPP(ETexelFormat Format)
{
switch (Format)
{
case ETexelFormat::GX_I4: return 4;
case ETexelFormat::GX_I8: return 8;
case ETexelFormat::GX_IA4: return 8;
case ETexelFormat::GX_IA8: return 16;
case ETexelFormat::GX_C4: return 4;
case ETexelFormat::GX_C8: return 8;
case ETexelFormat::GX_RGB565: return 16;
case ETexelFormat::GX_RGB5A3: return 16;
case ETexelFormat::GX_RGBA8: return 32;
case ETexelFormat::GX_CMPR: return 4;
case ETexelFormat::Luminance: return 8;
case ETexelFormat::LuminanceAlpha: return 16;
case ETexelFormat::RGBA4: return 16;
case ETexelFormat::RGB565: return 16;
case ETexelFormat::RGBA8: return 32;
case ETexelFormat::DXT1: return 4;
default: return 0;
}
}
// ************ PRIVATE ************
void CTexture::CalcLinearSize()
{
float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f;
mLinearSize = (uint32) (mWidth * mHeight * BytesPerPixel);
}
uint32 CTexture::CalcTotalSize()
{
float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f;
uint32 MipW = mWidth, MipH = mHeight;
uint32 Size = 0;
for (uint32 iMip = 0; iMip < mNumMipMaps; iMip++)
{
Size += (uint32) (MipW * MipH * BytesPerPixel);
MipW /= 2;
MipH /= 2;
}
return Size;
}
void CTexture::CopyGLBuffer()
{
if (!mGLBufferExists) return;
// Clear existing buffer
if (mBufferExists)
{
delete[] mpImgDataBuffer;
mBufferExists = false;
mpImgDataBuffer = nullptr;
mImgDataSize = 0;
}
// Calculate buffer size
mImgDataSize = CalcTotalSize();
mpImgDataBuffer = new uint8[mImgDataSize];
mBufferExists = true;
// Get texture
uint32 MipW = mWidth, MipH = mHeight, MipOffset = 0;
float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f;
GLenum BindTarget = (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D);
glBindTexture(BindTarget, mTextureID);
for (uint32 iMip = 0; iMip < mNumMipMaps; iMip++)
{
void *pData = mpImgDataBuffer + MipOffset;
glGetTexImage(BindTarget, iMip, GL_RGBA, GL_UNSIGNED_BYTE, pData);
MipOffset += (uint32) (MipW * MipH * BytesPerPixel);
MipW /= 2;
MipH /= 2;
}
mTexelFormat = ETexelFormat::RGBA8;
mLinearSize = mWidth * mHeight * 4;
}
void CTexture::DeleteBuffers()
{
if (mBufferExists)
{
delete[] mpImgDataBuffer;
mBufferExists = false;
mpImgDataBuffer = nullptr;
mImgDataSize = 0;
}
if (mGLBufferExists)
{
glDeleteTextures(1, &mTextureID);
mGLBufferExists = false;
}
}

View File

@ -1,70 +0,0 @@
#ifndef CTEXTURE_H
#define CTEXTURE_H
#include "CResource.h"
#include "ETexelFormat.h"
#include <Common/BasicTypes.h>
#include <Common/FileIO.h>
#include <Common/Math/CVector2f.h>
#include <GL/glew.h>
class CTexture : public CResource
{
DECLARE_RESOURCE_TYPE(Texture)
friend class CTextureDecoder;
friend class CTextureEncoder;
ETexelFormat mTexelFormat; // Format of decoded image data
ETexelFormat mSourceTexelFormat; // Format of input TXTR file
uint16 mWidth, mHeight; // Image dimensions
uint32 mNumMipMaps; // The number of mipmaps this texture has
uint32 mLinearSize; // The size of the top level mipmap, in bytes
bool mEnableMultisampling; // Whether multisample should be enabled (if this texture is a render target).
bool mBufferExists; // Indicates whether image data buffer has valid data
uint8 *mpImgDataBuffer; // Pointer to image data buffer
uint32 mImgDataSize; // Size of image data buffer
bool mGLBufferExists; // Indicates whether GL buffer has valid data
GLuint mTextureID; // ID for texture GL buffer
public:
CTexture(CResourceEntry *pEntry = 0);
CTexture(uint32 Width, uint32 Height);
~CTexture();
bool BufferGL();
void Bind(uint32 GLTextureUnit);
void Resize(uint32 Width, uint32 Height);
float ReadTexelAlpha(const CVector2f& rkTexCoord);
bool WriteDDS(IOutputStream& rOut);
// Accessors
ETexelFormat TexelFormat() const { return mTexelFormat; }
ETexelFormat SourceTexelFormat() const { return mSourceTexelFormat; }
uint32 Width() const { return (uint32) mWidth; }
uint32 Height() const { return (uint32) mHeight; }
uint32 NumMipMaps() const { return mNumMipMaps; }
GLuint TextureID() const { return mTextureID; }
inline void SetMultisamplingEnabled(bool Enable)
{
if (mEnableMultisampling != Enable)
DeleteBuffers();
mEnableMultisampling = Enable;
}
// Static
static uint32 FormatBPP(ETexelFormat Format);
// Private
private:
void CalcLinearSize();
uint32 CalcTotalSize();
void CopyGLBuffer();
void DeleteBuffers();
};
#endif // CTEXTURE_H

View File

@ -1,3 +1,4 @@
#if 0
#include "CTextureEncoder.h"
#include <Common/Log.h>
@ -10,12 +11,12 @@ void CTextureEncoder::WriteTXTR(IOutputStream& rTXTR)
{
// Only DXT1->CMPR supported at the moment
rTXTR.WriteLong((uint) mOutputFormat);
rTXTR.WriteShort(mpTexture->mWidth);
rTXTR.WriteShort(mpTexture->mHeight);
rTXTR.WriteLong(mpTexture->mNumMipMaps);
uint32 MipW = mpTexture->Width() / 4;
uint32 MipH = mpTexture->Height() / 4;
rTXTR.WriteShort(mpTexture->SizeX());
rTXTR.WriteShort(mpTexture->SizeY());
rTXTR.WriteLong(mpTexture->NumMipMaps());
/*
uint32 MipW = mpTexture->SizeX() / 4;
uint32 MipH = mpTexture->SizeY() / 4;
CMemoryInStream Image(mpTexture->mpImgDataBuffer, mpTexture->mImgDataSize, EEndian::LittleEndian);
uint32 MipOffset = Image.Tell();
@ -37,7 +38,7 @@ void CTextureEncoder::WriteTXTR(IOutputStream& rTXTR)
MipH /= 2;
if (MipW < 2) MipW = 2;
if (MipH < 2) MipH = 2;
}
}*/
}
void CTextureEncoder::DetermineBestOutputFormat()
@ -61,7 +62,7 @@ void CTextureEncoder::ReadSubBlockCMPR(IInputStream& rSource, IOutputStream& rDe
// ************ STATIC ************
void CTextureEncoder::EncodeTXTR(IOutputStream& rTXTR, CTexture *pTex)
{
if (pTex->mTexelFormat != ETexelFormat::DXT1)
if (pTex->mEditorFormat != ETexelFormat::BC1)
{
errorf("Unsupported texel format for decoding");
return;
@ -69,7 +70,7 @@ void CTextureEncoder::EncodeTXTR(IOutputStream& rTXTR, CTexture *pTex)
CTextureEncoder Encoder;
Encoder.mpTexture = pTex;
Encoder.mSourceFormat = ETexelFormat::DXT1;
Encoder.mSourceFormat = ETexelFormat::BC1;
Encoder.mOutputFormat = ETexelFormat::GX_CMPR;
Encoder.WriteTXTR(rTXTR);
}
@ -86,10 +87,9 @@ ETexelFormat CTextureEncoder::GetGXFormat(ETexelFormat Format)
{
case ETexelFormat::Luminance: return ETexelFormat::GX_I8;
case ETexelFormat::LuminanceAlpha: return ETexelFormat::GX_IA8;
case ETexelFormat::RGBA4: return ETexelFormat::GX_RGB5A3;
case ETexelFormat::RGB565: return ETexelFormat::GX_RGB565;
case ETexelFormat::RGBA8: return ETexelFormat::GX_RGBA8;
case ETexelFormat::DXT1: return ETexelFormat::GX_CMPR;
case ETexelFormat::BC1: return ETexelFormat::GX_CMPR;
default: return ETexelFormat::Invalid;
}
}
@ -103,7 +103,8 @@ ETexelFormat CTextureEncoder::GetFormat(ETexelFormat Format)
case ETexelFormat::GX_IA4: return ETexelFormat::LuminanceAlpha;
case ETexelFormat::GX_IA8: return ETexelFormat::LuminanceAlpha;
// todo rest of these
case ETexelFormat::GX_CMPR: return ETexelFormat::DXT1;
case ETexelFormat::GX_CMPR: return ETexelFormat::BC1;
default: return ETexelFormat::Invalid;
}
}
#endif

View File

@ -1,7 +1,8 @@
#ifndef CTEXTUREENCODER_H
#define CTEXTUREENCODER_H
#include "Core/Resource/CTexture.h"
#if 0
#include "Core/Resource/Texture/CTexture.h"
#include "Core/Resource/TResPtr.h"
// Class contains basic functionality right now - only supports directly converting DXT1 to CMPR
@ -23,5 +24,6 @@ public:
static ETexelFormat GetGXFormat(ETexelFormat Format);
static ETexelFormat GetFormat(ETexelFormat Format);
};
#endif
#endif // CTEXTUREENCODER_H

View File

@ -1,38 +0,0 @@
#ifndef ETEXELFORMAT
#define ETEXELFORMAT
// ETexelFormat - supported internal formats for decoded textures
enum class ETexelFormat
{
// Supported texel formats in GX using Retro's numbering
GX_I4 = 0x0,
GX_I8 = 0x1,
GX_IA4 = 0x2,
GX_IA8 = 0x3,
GX_C4 = 0x4,
GX_C8 = 0x5,
GX_C14x2 = 0x6,
GX_RGB565 = 0x7,
GX_RGB5A3 = 0x8,
GX_RGBA8 = 0x9,
GX_CMPR = 0xA,
// Supported internal texel formats for decoded textures
Luminance,
LuminanceAlpha,
RGBA4,
RGB565,
RGBA8,
DXT1,
Invalid = -1
};
// EGXPaletteFormat - GX's supported palette texel formats for C4/C8
enum class EGXPaletteFormat
{
IA8 = 0,
RGB565 = 1,
RGB5A3 = 2
};
#endif // ETEXELFORMAT

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
#ifndef CTEXTUREDECODER_H
#define CTEXTUREDECODER_H
#include "Core/Resource/CTexture.h"
#include "Core/Resource/ETexelFormat.h"
#include "Core/Resource/Texture/CTexture.h"
#include "Core/Resource/Texture/ETexelFormat.h"
#include <Common/BasicTypes.h>
#include <Common/CColor.h>
@ -10,86 +10,44 @@
class CTextureDecoder
{
CResourceEntry *mpEntry;
ETexelFormat mTexelFormat;
uint16 mWidth, mHeight;
CResourceEntry* mpEntry;
CTexture* mpTexture;
// Texture asset data
EGXTexelFormat mTexelFormat;
uint16 mSizeX, mSizeY;
uint32 mNumMipMaps;
bool mHasPalettes;
EGXPaletteFormat mPaletteFormat;
std::vector<uint8> mPalettes;
CMemoryInStream mPaletteInput;
struct SDDSInfo
{
enum { DXT1, DXT2, DXT3, DXT4, DXT5, RGBA } Format;
uint32 Flags;
uint32 BitCount;
uint32 RBitMask, GBitMask, BBitMask, ABitMask;
uint32 RShift, GShift, BShift, AShift;
uint32 RSize, GSize, BSize, ASize;
} mDDSInfo;
uint8 *mpDataBuffer;
uint32 mDataBufferSize;
// Private Functions
CTextureDecoder();
~CTextureDecoder();
CTexture* CreateTexture();
// Read
void ReadTXTR(IInputStream& rTXTR);
void ReadDDS(IInputStream& rDDS);
// Palette data
std::vector<uint8> mPaletteData;
uint32 mPaletteTexelStride;
// Decode
void PartialDecodeGXTexture(IInputStream& rTXTR);
void FullDecodeGXTexture(IInputStream& rTXTR);
void DecodeDDS(IInputStream& rDDS);
void DecodeGXTexture(IInputStream& TXTR);
void ParseTexel(EGXTexelFormat Format, IInputStream& Src, IOutputStream& Dst);
void ParseI4(IInputStream& Src, IOutputStream& Dst);
void ParseI8(IInputStream& Src, IOutputStream& Dst);
void ParseIA4(IInputStream& Src, IOutputStream& Dst);
void ParseIA8(IInputStream& Src, IOutputStream& Dst);
void ParseC4(IInputStream& Src, IOutputStream& Dst);
void ParseC8(IInputStream& Src, IOutputStream& Dst);
void ParseRGB565(IInputStream& Src, IOutputStream& Dst);
void ParseRGB5A3(IInputStream& Src, IOutputStream& Dst);
void ParseRGBA8(IInputStream& Src, IOutputStream& Dst);
void ParseCMPR(IInputStream& Src, IOutputStream& Dst);
// Decode Pixels (preserve compression)
void ReadPixelsI4(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelI8(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelIA4(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelIA8(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelsC4(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelC8(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelRGB565(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelRGB5A3(IInputStream& rSrc, IOutputStream& rDst);
void ReadPixelRGBA8(IInputStream& rSrc, IOutputStream& rDst);
void ReadSubBlockCMPR(IInputStream& rSrc, IOutputStream& rDst);
CTextureDecoder(CResourceEntry* pEntry);
~CTextureDecoder();
CTexture* ReadTXTR(IInputStream& TXTR);
// Decode Pixels (convert to RGBA8)
CColor DecodePixelI4(uint8 Byte, uint8 WhichPixel);
CColor DecodePixelI8(uint8 Byte);
CColor DecodePixelIA4(uint8 Byte);
CColor DecodePixelIA8(uint16 Short);
CColor DecodePixelC4(uint8 Byte, uint8 WhichPixel, IInputStream& rPaletteStream);
CColor DecodePixelC8(uint8 Byte, IInputStream& rPaletteStream);
CColor DecodePixelRGB565(uint16 Short);
CColor DecodePixelRGB5A3(uint16 Short);
CColor DecodePixelRGBA8(IInputStream& rSrc, IOutputStream& rDst);
void DecodeSubBlockCMPR(IInputStream& rSrc, IOutputStream& rDst, uint16 Width);
void DecodeBlockBC1(IInputStream& rSrc, IOutputStream& rDst, uint32 Width);
void DecodeBlockBC2(IInputStream& rSrc, IOutputStream& rDst, uint32 Width);
void DecodeBlockBC3(IInputStream& rSrc, IOutputStream& rDst, uint32 Width);
CColor DecodeDDSPixel(IInputStream& rDDS);
// Static
public:
static CTexture* LoadTXTR(IInputStream& rTXTR, CResourceEntry *pEntry);
static CTexture* LoadDDS(IInputStream& rDDS, CResourceEntry *pEntry);
static CTexture* DoFullDecode(IInputStream& rTXTR, CResourceEntry *pEntry);
static CTexture* DoFullDecode(CTexture *pTexture);
static CTexture* LoadTXTR(IInputStream& TXTR, CResourceEntry* pEntry);
// Utility
static uint8 Extend3to8(uint8 In);
static uint8 Extend4to8(uint8 In);
static uint8 Extend5to8(uint8 In);
static uint8 Extend6to8(uint8 In);
static uint32 CalculateShiftForMask(uint32 BitMask);
static uint32 CalculateMaskBitCount(uint32 BitMask);
};
#endif // CTEXTUREDECODER_H

View File

@ -1,11 +1,15 @@
#ifndef RESOURCES_H
#define RESOURCES_H
#include "CAudioGroup.h"
#include "CAudioLookupTable.h"
#include "CAudioMacro.h"
#include "CDependencyGroup.h"
#include "CFont.h"
#include "CMapArea.h"
#include "CPoiToWorld.h"
#include "CResource.h"
#include "CTexture.h"
#include "CStringList.h"
#include "CWorld.h"
#include "Core/Resource/Animation/CAnimation.h"
#include "Core/Resource/Animation/CAnimSet.h"
@ -16,6 +20,7 @@
#include "Core/Resource/Model/CModel.h"
#include "Core/Resource/Scan/CScan.h"
#include "Core/Resource/StringTable/CStringTable.h"
#include "Core/Resource/Texture/CTexture.h"
#endif // RESOURCES_H

View File

@ -0,0 +1,275 @@
#include "CTexture.h"
#include "NTextureUtils.h"
#include <Common/Math/MathUtil.h>
CTexture::CTexture(CResourceEntry *pEntry /*= 0*/)
: CResource(pEntry)
, mEditorFormat(ETexelFormat::RGBA8)
, mGameFormat(EGXTexelFormat::RGBA8)
, mEnableMultisampling(false)
, mTextureResource(0)
{
}
CTexture::CTexture(uint32 SizeX, uint32 SizeY)
: mEditorFormat(ETexelFormat::RGBA8)
, mGameFormat(EGXTexelFormat::RGBA8)
, mEnableMultisampling(false)
, mTextureResource(0)
{
mMipData.emplace_back();
SMipData& Mip = mMipData.back();
Mip.SizeX = SizeX;
Mip.SizeY = SizeY;
Mip.DataBuffer.resize(SizeX * SizeY * 4);
}
CTexture::~CTexture()
{
ReleaseRenderResources();
}
void CTexture::CreateRenderResources()
{
if (mTextureResource != 0)
{
ReleaseRenderResources();
}
GLenum BindTarget = (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D);
glGenTextures(1, &mTextureResource);
glBindTexture(BindTarget, mTextureResource);
GLenum GLFormat, GLType;
switch (mEditorFormat)
{
case ETexelFormat::Luminance:
GLFormat = GL_R8;
GLType = GL_UNSIGNED_BYTE;
break;
case ETexelFormat::LuminanceAlpha:
GLFormat = GL_RG8;
GLType = GL_UNSIGNED_BYTE;
break;
case ETexelFormat::RGB565:
GLFormat = GL_RGB;
GLType = GL_UNSIGNED_SHORT_5_6_5;
break;
case ETexelFormat::RGBA8:
GLFormat = GL_RGBA;
GLType = GL_UNSIGNED_BYTE;
break;
}
// The smallest mipmaps are probably not being loaded correctly, because mipmaps in GX textures have a minimum size depending on the format, and these don't.
// Not sure specifically what accomodations should be made to fix that though so whatever.
for (uint MipIdx = 0; MipIdx < mMipData.size(); MipIdx++)
{
const SMipData& MipData = mMipData[MipIdx];
uint SizeX = MipData.SizeX;
uint SizeY = MipData.SizeY;
const void* pkData = MipData.DataBuffer.data();
if (mEnableMultisampling)
{
glTexImage2DMultisample(BindTarget, 4, GLFormat, SizeX, SizeY, true);
}
else
{
glTexImage2D(BindTarget, MipIdx, GLFormat, SizeX, SizeY, 0, GLFormat, GLType, pkData);
}
}
glTexParameteri(BindTarget, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(BindTarget, GL_TEXTURE_MAX_LEVEL, mMipData.size() - 1);
// Linear filtering on mipmaps:
glTexParameteri(BindTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(BindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Anisotropic filtering:
float MaxAnisotropy;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &MaxAnisotropy);
glTexParameterf(BindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, MaxAnisotropy);
// Swizzle for luminance formats
if (mEditorFormat == ETexelFormat::Luminance)
{
glTexParameteri(BindTarget, GL_TEXTURE_SWIZZLE_RGBA, GL_RED);
}
else if (mEditorFormat == ETexelFormat::LuminanceAlpha)
{
glTexParameteri(BindTarget, GL_TEXTURE_SWIZZLE_R, GL_RED);
glTexParameteri(BindTarget, GL_TEXTURE_SWIZZLE_G, GL_RED);
glTexParameteri(BindTarget, GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexParameteri(BindTarget, GL_TEXTURE_SWIZZLE_A, GL_GREEN);
}
}
void CTexture::ReleaseRenderResources()
{
if (mTextureResource != 0)
{
glDeleteTextures(1, &mTextureResource);
mTextureResource = 0;
}
}
void CTexture::BindToSampler(uint SamplerIndex) const
{
// CreateGraphicsResources() must have been called before calling this
// @todo this should not be the responsibility of CTexture
ASSERT( mTextureResource != 0 );
GLenum BindTarget = (mEnableMultisampling ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0 + SamplerIndex);
glBindTexture(BindTarget, mTextureResource);
}
/** Generate mipmap chain based on the contents of the first mip */
void CTexture::GenerateMipTail(uint NumMips /*= 0*/)
{
//@todo
}
/** Allocate mipmap data, but does not fill any data. Returns the new mipmap count. */
uint CTexture::AllocateMipTail(uint DesiredMipCount /*= 0*/)
{
// We must have at least one mipmap to start with.
if (mMipData.empty())
{
warnf("Failed to allocate mip tail; texture is empty, did not initialize correctly");
return 0;
}
// Try to allocate the requested number of mipmaps, but don't allocate any below 1x1.
// Also, we always need at least one mipmap.
uint BaseSizeX = mMipData[0].SizeX;
uint BaseSizeY = mMipData[0].SizeY;
uint MaxMips = Math::Min( Math::FloorLog2(BaseSizeX), Math::FloorLog2(BaseSizeY) ) + 1;
uint NewMipCount = Math::Min(MaxMips, DesiredMipCount);
if (mMipData.size() != NewMipCount)
{
uint OldMipCount = mMipData.size();
mMipData.resize(NewMipCount);
// Allocate internal data for any new mips.
if (NewMipCount > OldMipCount)
{
uint LastMipIdx = OldMipCount - 1;
uint SizeX = mMipData[LastMipIdx].SizeX;
uint SizeY = mMipData[LastMipIdx].SizeY;
uint BPP = NTextureUtils::GetTexelFormatInfo( mEditorFormat ).BitsPerPixel;
for (uint MipIdx = OldMipCount; MipIdx < NewMipCount; MipIdx++)
{
SizeX /= 2;
SizeY /= 2;
uint Size = (SizeX * SizeY * BPP) / 8;
mMipData[MipIdx].SizeX = SizeX;
mMipData[MipIdx].SizeY = SizeY;
mMipData[MipIdx].DataBuffer.resize(Size);
}
}
}
return mMipData.size();
}
/** Compress the texture data into the format specified by Format. */
void CTexture::Compress(EGXTexelFormat Format)
{
// @todo load source PNG data
}
/** Generate editor texel data based on the currently loaded game texel data */
void CTexture::GenerateEditorData(bool bClearGameData /*= true*/)
{
for (uint MipIdx = 0; MipIdx < mMipData.size(); MipIdx++)
{
SMipData& MipData = mMipData[MipIdx];
NTextureUtils::ConvertGameDataToEditorData(
mGameFormat,
MipData.SizeX,
MipData.SizeY,
MipData.GameDataBuffer.data(),
MipData.GameDataBuffer.size(),
MipData.DataBuffer,
MipIdx == 0 ? &mEditorFormat : nullptr
);
if (bClearGameData)
{
MipData.GameDataBuffer.clear();
}
}
}
/**
* Update the internal resolution of the texture; used for dynamically-scaling textures
*/
void CTexture::Resize(uint32 SizeX, uint32 SizeY)
{
if (mMipData.size() > 0)
{
if (mMipData[0].SizeX == SizeX &&
mMipData[0].SizeY == SizeY)
{
return;
}
}
if (mMipData.size() == 0)
{
mMipData.emplace_back();
}
const STexelFormatInfo& kFormatInfo = NTextureUtils::GetTexelFormatInfo(mEditorFormat);
mMipData.back().SizeX = SizeX;
mMipData.back().SizeY = SizeY;
mMipData.back().DataBuffer.resize( SizeX * SizeY * kFormatInfo.BitsPerPixel / 8 );
if (mTextureResource != 0)
{
ReleaseRenderResources();
CreateRenderResources();
}
}
float CTexture::ReadTexelAlpha(const CVector2f& kTexCoord)
{
// @todo: this is an inaccurate implementation because it
// doesn't take into account mipmaps or texture filtering
const SMipData& kMipData = mMipData[0];
uint32 TexelX = (uint32) ((kMipData.SizeX - 1) * kTexCoord.X);
uint32 TexelY = (uint32) ((kMipData.SizeY - 1) * (1.f - fmodf(kTexCoord.Y, 1.f)));
if (mEditorFormat == ETexelFormat::Luminance || mEditorFormat == ETexelFormat::RGB565)
{
// No alpha in these formats
return 1.f;
}
else if (mEditorFormat == ETexelFormat::LuminanceAlpha)
{
uint Offset = (TexelY * kMipData.SizeX * 2) + TexelX*2 + 1;
uint8 Alpha = *((uint8*) &kMipData.DataBuffer[Offset]);
return Alpha / 255.f;
}
else if (mEditorFormat == ETexelFormat::RGBA8)
{
uint Offset = (TexelY * kMipData.SizeX * 4) + TexelX*4 + 3;
uint8 Alpha = *((uint8*) &kMipData.DataBuffer[Offset]);
return Alpha / 255.f;
}
else
{
errorf("Unhandled texel format in ReadTexelAlpha(): %s",
TEnumReflection<ETexelFormat>::ConvertValueToString(mEditorFormat));
return 1.f;
}
}

View File

@ -0,0 +1,98 @@
#ifndef CTEXTURE_H
#define CTEXTURE_H
#include "Core/Resource/CResource.h"
#include "ETexelFormat.h"
#include <Common/BasicTypes.h>
#include <Common/FileIO.h>
#include <Common/Math/CVector2f.h>
#include <GL/glew.h>
/** Mipmap data */
struct SMipData
{
/** Mip dimensions */
uint SizeX, SizeY;
/** Mip texel data */
std::vector<uint8> DataBuffer;
/** Mip texel data with game texel formats; may be empty when not in use */
std::vector<uint8> GameDataBuffer;
};
/** Class representing a 2D texture asset */
class CTexture : public CResource
{
DECLARE_RESOURCE_TYPE(Texture)
friend class CTextureDecoder;
friend class CTextureEncoder;
/** Format of encoded image data in-game */
EGXTexelFormat mGameFormat;
/** Format of decoded image data in-editor */
ETexelFormat mEditorFormat;
/** Mipmap data */
std::vector<SMipData> mMipData;
/** @todo the following is OpenGL stuff that really shouldn't be implemented here */
/** Whether multisample should be enabled (if this texture is a render target). */
bool mEnableMultisampling;
/** OpenGL texture resource handle */
GLuint mTextureResource;
public:
/** Constructors */
CTexture(CResourceEntry* pEntry = 0);
CTexture(uint SizeX, uint SizeY);
~CTexture();
/** Generate mipmap chain based on the contents of the first mip */
void GenerateMipTail(uint NumMips = 0);
/** Allocate mipmap data, but does not fill any data. Returns the new mipmap count. */
uint AllocateMipTail(uint DesiredMipCount = 0);
/** Compress the texture data into the format specified by Format. */
void Compress(EGXTexelFormat Format);
/** Generate editor texel data based on the currently loaded game texel data */
void GenerateEditorData(bool bClearGameData = true);
/**
* Update the internal resolution of the texture; used for dynamically-scaling textures
* @todo - should not be implemented here as these textures are not relevant to texture assets
*/
void Resize(uint SizeX, uint SizeY);
/** Return the alpha value of the texel at the given coordinates */
float ReadTexelAlpha(const CVector2f& rkTexCoord);
/** Create/release resources for rendering this texture */
void CreateRenderResources();
void ReleaseRenderResources();
// Accessors
FORCEINLINE ETexelFormat EditorTexelFormat() const { return mEditorFormat; }
FORCEINLINE EGXTexelFormat GameTexelFormat() const { return mGameFormat; }
FORCEINLINE uint SizeX() const { return mMipData.empty() ? 0 : mMipData[0].SizeX; }
FORCEINLINE uint SizeY() const { return mMipData.empty() ? 0 : mMipData[0].SizeY; }
FORCEINLINE uint NumMipMaps() const { return mMipData.size(); }
FORCEINLINE GLuint RenderResource() const { return mTextureResource; }
FORCEINLINE SMipData& GetMipData(uint Idx) { return mMipData[Idx]; }
/** @todo these functions shouldn't be handled in this class. */
void BindToSampler(uint SamplerIndex) const;
FORCEINLINE void SetMultisamplingEnabled(bool Enable)
{
mEnableMultisampling = Enable;
}
};
#endif // CTEXTURE_H

View File

@ -0,0 +1,60 @@
#ifndef ETEXELFORMAT
#define ETEXELFORMAT
#include <Common/BasicTypes.h>
/** Texel formats in GX using Retro's indexing from the TXTR format */
enum class EGXTexelFormat
{
// 4-bit single-channel greyscale
I4 = 0x0,
// 8-bit single-channel greyscale
I8 = 0x1,
// 4-bit two-channel greyscale with alpha
IA4 = 0x2,
// 8-bit two-channel greyscale with alpha
IA8 = 0x3,
// Palette with 4-bit indices
C4 = 0x4,
// Palette with 8-bit indices
C8 = 0x5,
// Unused
C14x2 = 0x6,
// 16-bit three-channel RGB texture
RGB565 = 0x7,
// 16-bit four-channel RGBA texture
RGB5A3 = 0x8,
// 32-bit four-channel uncompressed RGBA texture
RGBA8 = 0x9,
// Compressed CMPR (similar to BC1/DXT1) RGBA texture with one-bit alpha
CMPR = 0xA,
Invalid = -1
};
/** Texel formats useed internally in the editor */
enum class ETexelFormat
{
// Single-channel greyscale
Luminance,
// Two-channel greyscale with alpha
LuminanceAlpha,
// Three-channel 16-bit RGB texture
RGB565,
// Four-channel 32-bit uncompressed RGBA texture
RGBA8,
Invalid = -1
};
/** Info about texel formats; retrieve via NTextureUtils */
struct STexelFormatInfo
{
EGXTexelFormat GameTexelFormat;
ETexelFormat EditorTexelFormat;
uint BlockSizeX;
uint BlockSizeY;
uint BitsPerPixel;
};
#endif // ETEXELFORMAT

View File

@ -0,0 +1,446 @@
#include "NTextureUtils.h"
#include <Common/Common.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ASSERT ASSERT
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
namespace NTextureUtils
{
/** Table of image format info; indexed by EGXTexelFormat. */
STexelFormatInfo kTexelFormatInfo[] =
{
// GameTexelFormat EditorTexelFormat BlockSizeX BlockSizeY BitsPerPixel
{ EGXTexelFormat::I4, ETexelFormat::Luminance, 8, 8, 4 },
{ EGXTexelFormat::I8, ETexelFormat::Luminance, 8, 4, 8 },
{ EGXTexelFormat::IA4, ETexelFormat::LuminanceAlpha, 8, 4, 8 },
{ EGXTexelFormat::IA8, ETexelFormat::LuminanceAlpha, 4, 4, 16 },
{ EGXTexelFormat::C4, ETexelFormat::Invalid, 8, 8, 4 },
{ EGXTexelFormat::C8, ETexelFormat::Invalid, 8, 4, 8 },
{ EGXTexelFormat::C14x2, ETexelFormat::Invalid, 4, 4, 16 },
{ EGXTexelFormat::RGB565, ETexelFormat::RGB565, 4, 4, 16 },
{ EGXTexelFormat::RGB5A3, ETexelFormat::RGBA8, 4, 4, 16 },
{ EGXTexelFormat::RGBA8, ETexelFormat::RGBA8, 4, 4, 32 },
{ EGXTexelFormat::CMPR, ETexelFormat::RGBA8, 8, 8, 4 },
};
/** Remap an ETexelFormat to the closest EGXTexelFormat */
const EGXTexelFormat kEditorFormatToGameFormat[] =
{
// Luminance
EGXTexelFormat::I8,
// LuminanceAlpha
EGXTexelFormat::IA8,
// RGB565
EGXTexelFormat::RGB565,
// RGBA8
EGXTexelFormat::RGBA8,
};
/** Retrieve the format info for a given texel format */
const STexelFormatInfo& GetTexelFormatInfo(EGXTexelFormat Format)
{
uint FormatIdx = (uint) Format;
ASSERT( FormatIdx >= 0 && FormatIdx < ARRAY_SIZE(kTexelFormatInfo) );
return kTexelFormatInfo[(uint) Format];
}
const STexelFormatInfo& GetTexelFormatInfo(ETexelFormat Format)
{
return GetTexelFormatInfo( kEditorFormatToGameFormat[(uint) Format] );
}
/** Utility functions useful for converting image formats */
FORCEINLINE static uint8 Extend3to8(uint8 In)
{
In &= 0x7;
return (In << 5) | (In << 2) | (In >> 1);
}
FORCEINLINE static uint8 Extend4to8(uint8 In)
{
In &= 0xF;
return (In << 4) | In;
}
FORCEINLINE static uint8 Extend5to8(uint8 In)
{
In &= 0x1F;
return (In << 3) | (In >> 2);
}
FORCEINLINE static uint8 Extend6to8(uint8 In)
{
In &= 0x3F;
return (In << 2) | (In >> 4);
}
/** Decompose an RGBA dword into 8-bit components */
FORCEINLINE static void DecomposeRGBA8(uint32 In,
uint8& OutR,
uint8& OutG,
uint8& OutB,
uint8& OutA)
{
OutR = (In >> 24) & 0xFF;
OutG = (In >> 16) & 0xFF;
OutB = (In >> 8) & 0xFF;
OutA = (In >> 0) & 0xFF;
}
/** Compose an RGBA dword from 8-bit components */
FORCEINLINE static uint32 ComposeRGBA8(uint8 R,
uint8 G,
uint8 B,
uint8 A)
{
return (R << 24) | (G << 16) | (B << 8) | A;
}
/** Convert an RGB5A3 word into an RGBA8 dword */
static uint32 RGB5A3toRGBA8(uint16 Texel)
{
// RGB5A3 uses a per-texel sign bit to swap between two formats.
// Based on this bit, the texel is either RGB555 or RGB4A3.
uint8 R, G, B, A;
if (Texel & 0x8000)
{
R = Extend5to8(Texel >> 10);
G = Extend5to8(Texel >> 5);
B = Extend5to8(Texel >> 0);
A = 255;
}
else
{
R = Extend4to8(Texel >> 11);
G = Extend4to8(Texel >> 7);
B = Extend4to8(Texel >> 3);
A = Extend3to8(Texel >> 0);
}
return ComposeRGBA8(R, G, B, A);
}
/** Convert an RGB565 word into an RGBA8 dword */
static uint32 RGB565toRGBA8(uint16 Texel)
{
uint8 R = Extend5to8( (Texel >> 11) & 0x1F );
uint8 G = Extend6to8( (Texel >> 5) & 0x3F );
uint8 B = Extend5to8( (Texel >> 0) & 0x1F );
return ComposeRGBA8(R, G, B, 255);
}
/** A texel block in the CMPR/BC1 format */
struct SCMPRBlock
{
uint16 C0;
uint16 C1;
uint8 Idx[4];
};
/** Extract the four palette colors from a CMPR texel block */
static void DecomposeCMPRPalettes(const SCMPRBlock& kBlock,
uint32& OutC0,
uint32& OutC1,
uint32& OutC2,
uint32& OutC3)
{
// Get block palette colors and decompose into RGBA components
uint8 R0, G0, B0, A0,
R1, G1, B1, A1,
R2, G2, B2, A2,
R3, G3, B3, A3;
OutC0 = RGB565toRGBA8(kBlock.C0);
OutC1 = RGB565toRGBA8(kBlock.C1);
DecomposeRGBA8(OutC0, R0, G0, B0, A0);
DecomposeRGBA8(OutC1, R1, G1, B1, A1);
// Interpolate to get the remaining palette colors
if (kBlock.C0 > kBlock.C1)
{
// GameCube hardware interpolates at 3/8 and 5/8 points
// This differs from PC DXT1 implementation that interpolates at 1/3 and 2/3
R2 = (R0*5 + R1*3) >> 3;
G2 = (G0*5 + G1*3) >> 3;
B2 = (B0*5 + B1*3) >> 3;
A2 = 255;
R3 = (R0*3 + R1*5) >> 3;
G3 = (G0*3 + G1*5) >> 3;
B3 = (B0*3 + B1*5) >> 3;
A3 = 255;
}
else
{
// GameCube hardware sets the color of C3 as the same as C2 instead of black
R2 = R3 = (R0 + R1) / 2;
G2 = G3 = (G0 + G1) / 2;
B2 = B3 = (B0 + B1) / 2;
A2 = 255, A3 = 0;
}
OutC2 = ComposeRGBA8(R2, G2, B2, A2);
OutC3 = ComposeRGBA8(R3, G3, B3, A3);
}
/**
* Converts game texel data to the corresponding editor format.
* "Game Data" is essentially the texture data that is contained
* in a TXTR file, except without swizzling and with fixed endianness.
* The output texel format is the same as specified by GetTexelFormatInfo().
*/
void ConvertGameDataToEditorData(EGXTexelFormat SrcFormat,
uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
std::vector<uint8>& DstData,
ETexelFormat* pOutTexelFormat /*= nullptr*/)
{
//@todo palette formats are unsupported
const STexelFormatInfo& kSrcTexelFormat = GetTexelFormatInfo( SrcFormat );
const STexelFormatInfo& kDstTexelFormat = GetTexelFormatInfo( kSrcTexelFormat.EditorTexelFormat );
uint SrcSize = (SizeX * SizeY * kSrcTexelFormat.BitsPerPixel) / 8;
uint DstSize = (SizeX * SizeY * kDstTexelFormat.BitsPerPixel) / 8;
ASSERT( SrcDataSize >= SrcSize );
DstData.resize( DstSize );
memset(DstData.data(), 0xFF, DstData.size());
uint8* pDstData = DstData.data();
if (pOutTexelFormat)
{
*pOutTexelFormat = kDstTexelFormat.EditorTexelFormat;
}
// Some game formats are the same as the editor format.
// In these cases we can simply copy the buffer over.
if( SrcFormat == EGXTexelFormat::I8 ||
SrcFormat == EGXTexelFormat::IA8 ||
SrcFormat == EGXTexelFormat::RGB565 ||
SrcFormat == EGXTexelFormat::RGBA8 )
{
memcpy( pDstData, pkSrcData, DstSize );
}
// CMPR requires special handling. There are small differences between CMPR and DXT1
// that prevents us from using hardware DXT1 support, so it must be decoded to RGBA8.
else if( SrcFormat == EGXTexelFormat::CMPR )
{
const SCMPRBlock* pkBlock = (const SCMPRBlock*) pkSrcData;
uint32* pDst32 = (uint32*) pDstData;
for (uint Y = 0; Y < SizeY; Y += 4)
{
for (uint X = 0; X < SizeX; X += 4)
{
uint32 Palettes[4];
DecomposeCMPRPalettes(*pkBlock, Palettes[0], Palettes[1],
Palettes[2], Palettes[3] );
// Byte-swap the palettes so they will be written in RGBA order
if (EEndian::SystemEndian == EEndian::LittleEndian)
{
SwapBytes(Palettes[0]);
SwapBytes(Palettes[1]);
SwapBytes(Palettes[2]);
SwapBytes(Palettes[3]);
}
for (uint DY = 0; DY < 4; DY++)
{
uint8 Bits = pkBlock->Idx[DY];
for (uint DX = 0; DX < 4; DX++)
{
uint Shift = DX*2;
uint Index = (Bits >> Shift) & 0x3;
uint DstOffset = (Y+DY)*SizeX + X+DX;
pDst32[DstOffset] = Palettes[Index];
}
}
pkBlock++;
}
}
}
// Remaining formats require conversion because they aren't supported by PC graphics cards.
else
{
// Sweep texels left to right, top to bottom, and apply conversions.
for (uint Y = 0; Y < SizeY; Y++)
{
for (uint X = 0; X < SizeX; X++)
{
switch ( SrcFormat )
{
// I4/IA4: Extend greyscale and alpha (for IA4) to 8-bit.
// Note I4 has two 1-component texels, whereas IA4 has one 2-component texels.
// But this data is converted to the output data the same way either way
case EGXTexelFormat::I4:
case EGXTexelFormat::IA4:
{
uint8 I = *pkSrcData++;
*pDstData++ = Extend3to8( (I >> 4) & 0xF );
*pDstData++ = Extend3to8( (I >> 0) & 0xF );
break;
}
// RGB5A3: Convert to raw RGBA8.
case EGXTexelFormat::RGB5A3:
{
uint8 V0 = *pkSrcData++;
uint8 V1 = *pkSrcData++;
uint32 RGBA = RGB5A3toRGBA8( (V1 << 8) | V0 );
SwapBytes(RGBA);
*((uint32*) pDstData) = RGBA;
pDstData += 4;
break;
}
default:
// Unhandled
errorf("Unhandled texel format in ConvertGameDataToEditorData(): %s",
TEnumReflection<EGXTexelFormat>::ConvertValueToString(SrcFormat) );
return;
}
// Increment I4 again to account for the fact that we read two texels instead of one
if( SrcFormat == EGXTexelFormat::I4 )
{
X++;
}
}
}
}
}
/** Decode editor texel data to RGBA texels */
void ConvertEditorDataToRGBA(ETexelFormat SrcFormat,
uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
std::vector<uint8>& DstData)
{
const STexelFormatInfo& kFormatInfo = GetTexelFormatInfo(SrcFormat);
uint32 SrcSize = (SizeX * SizeY * kFormatInfo.BitsPerPixel) / 8;
uint32 DstSize = (SizeX * SizeY);
ASSERT( SrcDataSize >= SrcSize);
DstData.resize(DstSize);
uint8* pDstData = DstData.data();
// If data is already RGBA, then no conversion is needed.
if (SrcFormat == ETexelFormat::RGBA8)
{
memcpy( pDstData, pkSrcData, DstSize );
return;
}
// Other formats require conversion
for (uint Y = 0; Y < SizeY; Y++)
{
for (uint X = 0; X < SizeX; X++)
{
uint8 R, G, B, A;
switch (SrcFormat)
{
// Luminance: Replicate to all channels and set opaque alpha
case ETexelFormat::Luminance:
{
R = G = B = *pkSrcData++;
A = 255;
break;
}
// LuminanceAlpha: Replicate greyscale to all channels and preserve alpha
case ETexelFormat::LuminanceAlpha:
{
R = G = B = *pkSrcData++;
A = *pkSrcData++;
break;
}
// RGB565: Extend RGB components to 8-bit and set opaque alpha
case ETexelFormat::RGB565:
{
uint8 Byte0 = *pkSrcData++;
uint8 Byte1 = *pkSrcData++;
uint16 Texel = (Byte1 << 8) | Byte0;
uint32 Decoded = RGB565toRGBA8(Texel);
DecomposeRGBA8(Decoded, R, G, B, A);
break;
}
}
*pDstData++ = R;
*pDstData++ = G;
*pDstData++ = B;
*pDstData++ = A;
}
}
}
/** Encode RGBA texels into a game compression format */
void CompressRGBA(uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
EGXTexelFormat DstFormat,
std::vector<uint8>& DstData)
{
//@todo
}
/** Import the image file at the given path. Returns true if succeeded. */
bool LoadImageFromFile(const TString& kPath,
std::vector<uint8>& OutBuffer,
int& OutSizeX,
int& OutSizeY,
int& OutNumChannels,
int DesiredNumChannels /*= 4*/)
{
std::vector<uint8> DataBuffer;
FileUtil::LoadFileToBuffer(kPath, DataBuffer);
return LoadImageFromMemory(DataBuffer.data(), DataBuffer.size(), OutBuffer, OutSizeX, OutSizeY, OutNumChannels, DesiredNumChannels);
}
/** Import an image file from a buffer. Returns true if succeeded. */
bool LoadImageFromMemory(void* pData,
uint DataSize,
std::vector<uint8>& OutBuffer,
int& OutSizeX,
int& OutSizeY,
int& OutNumChannels,
int DesiredNumChannels /*= 4*/)
{
stbi_uc* pOutData = stbi_load_from_memory( (const stbi_uc*) pData, DataSize, &OutSizeX, &OutSizeY, &OutNumChannels, DesiredNumChannels );
if (pOutData)
{
uint32 NumChannels = (DesiredNumChannels > 0 ? DesiredNumChannels : OutNumChannels);
uint32 ImageSize = (OutSizeX * OutSizeY * NumChannels);
OutBuffer.resize(ImageSize);
memcpy(OutBuffer.data(), pOutData, ImageSize);
STBI_FREE(pOutData);
return true;
}
else
{
return false;
}
}
} // end namespace NImageUtils

View File

@ -0,0 +1,74 @@
#ifndef NTEXTUREUTILS_H
#define NTEXTUREUTILS_H
#include "ETexelFormat.h"
#include <Common/BasicTypes.h>
#include <Common/TString.h>
#include <vector>
/**
* Various utility functions for working with textures and images.
* For import functions, the following formats are supported:
* JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
**/
namespace NTextureUtils
{
/**
* Retrieve the format info for a given texel format.
* Note: EditorTexelFormat field is invalid for palette formats (C4, C8, C14x2).
* For these, fetch the format info of the underlying texel format.
*/
const STexelFormatInfo& GetTexelFormatInfo(EGXTexelFormat Format);
const STexelFormatInfo& GetTexelFormatInfo(ETexelFormat Format);
/**
* Converts game texel data to the corresponding editor format.
* "Game Data" is essentially the texture data that is contained
* in a TXTR file, except without swizzling and with fixed endianness.
* The output texel format is the same as specified by GetTexelFormatInfo().
*/
void ConvertGameDataToEditorData(EGXTexelFormat SrcFormat,
uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
std::vector<uint8>& DstData,
ETexelFormat* pOutTexelFormat = nullptr);
/** Decode editor texel data to RGBA texels */
void ConvertEditorDataToRGBA(ETexelFormat SrcFormat,
uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
std::vector<uint8>& DstData);
/** Encode RGBA texels into a game compression format */
void CompressRGBA(uint32 SizeX,
uint32 SizeY,
const uint8* pkSrcData,
uint SrcDataSize,
EGXTexelFormat DstFormat,
std::vector<uint8>& DstData);
/** Import the image file at the given path. Returns true if succeeded. */
bool LoadImageFromFile(const TString& kPath,
std::vector<uint8>& OutBuffer,
int& OutSizeX,
int& OutSizeY,
int& OutNumChannels,
int DesiredNumChannels = 0);
/** Import an image file from a buffer. Returns true if succeeded. */
bool LoadImageFromMemory(void* pData,
uint DataSize,
std::vector<uint8>& OutBuffer,
int& OutSizeX,
int& OutSizeY,
int& OutNumChannels,
int DesiredNumChannels = 0);
}
#endif // NTEXTUREUTILS_H

View File

@ -91,10 +91,7 @@ CModelEditorWindow::CModelEditorWindow(CModel *pModel, QWidget *pParent)
ui->AnimParamCSpinBox->setProperty ("ModelEditorWidgetType", (int) EModelEditorWidget::AnimParamCSpinBox);
ui->AnimParamDSpinBox->setProperty ("ModelEditorWidgetType", (int) EModelEditorWidget::AnimParamDSpinBox);
connect(ui->ActionImport, SIGNAL(triggered()), this, SLOT(Import()));
connect(ui->ActionSave, SIGNAL(triggered()), this, SLOT(Save()));
connect(ui->ActionConvertToDDS, SIGNAL(triggered()), this, SLOT(ConvertToDDS()));
connect(ui->ActionConvertToTXTR, SIGNAL(triggered()), this, SLOT(ConvertToTXTR()));
connect(ui->MeshPreviewButton, SIGNAL(clicked()), this, SLOT(SetMeshPreview()));
connect(ui->SpherePreviewButton, SIGNAL(clicked()), this, SLOT(SetSpherePreview()));
connect(ui->FlatPreviewButton, SIGNAL(clicked()), this, SLOT(SetFlatPreview()));
@ -765,55 +762,6 @@ void CModelEditorWindow::Import()
gpResourceStore->DestroyUnreferencedResources();
}
void CModelEditorWindow::ConvertToDDS()
{
QString Input = QFileDialog::getOpenFileName(this, "Retro Texture (*.TXTR)", "", "*.TXTR");
if (Input.isEmpty()) return;
TString TexFilename = TO_TSTRING(Input);
CFileInStream InTextureFile(TexFilename, EEndian::LittleEndian);
CTexture *pTex = CTextureDecoder::LoadTXTR( InTextureFile, nullptr );
TString OutName = TexFilename.GetFilePathWithoutExtension() + ".dds";
CFileOutStream Out(OutName, EEndian::LittleEndian);
if (!Out.IsValid()) QMessageBox::warning(this, "Error", "Couldn't open output DDS!");
else
{
bool Success = pTex->WriteDDS(Out);
if (!Success) QMessageBox::warning(this, "Error", "Couldn't write output DDS!");
else QMessageBox::information(this, "Success", "Successfully converted to DDS!");
}
delete pTex;
}
void CModelEditorWindow::ConvertToTXTR()
{
QString Input = QFileDialog::getOpenFileName(this, "DirectDraw Surface (*.dds)", "", "*.dds");
if (Input.isEmpty()) return;
TString TexFilename = TO_TSTRING(Input);
CFileInStream InTextureFile = CFileInStream(TexFilename, EEndian::LittleEndian);
CTexture *pTex = CTextureDecoder::LoadDDS(InTextureFile, nullptr);
TString OutName = TexFilename.GetFilePathWithoutExtension() + ".txtr";
if ((pTex->TexelFormat() != ETexelFormat::DXT1) || (pTex->NumMipMaps() > 1))
QMessageBox::warning(this, "Error", "Can't convert DDS to TXTR! Save your texture as a DXT1 DDS with no mipmaps, then try again.");
else
{
CFileOutStream Out(OutName, EEndian::BigEndian);
if (!Out.IsValid()) QMessageBox::warning(this, "Error", "Couldn't open output TXTR!");
else
{
CTextureEncoder::EncodeTXTR(Out, pTex, ETexelFormat::GX_CMPR);
QMessageBox::information(this, "Success", "Successfully converted to TXTR!");
}
}
}
void CModelEditorWindow::SetMeshPreview()
{
ui->Viewport->SetDrawMode(CModelEditorViewport::EDrawMode::DrawMesh);

View File

@ -101,8 +101,6 @@ private:
private slots:
void Import();
void ConvertToDDS();
void ConvertToTXTR();
void SetMeshPreview();
void SetSpherePreview();
void SetFlatPreview();

View File

@ -172,9 +172,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-489</y>
<width>308</width>
<height>1184</height>
<y>0</y>
<width>319</width>
<height>1302</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -2407,7 +2407,7 @@
<x>0</x>
<y>0</y>
<width>1280</width>
<height>21</height>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -2415,17 +2415,8 @@
<string>File</string>
</property>
<addaction name="ActionSave"/>
<addaction name="ActionImport"/>
</widget>
<widget class="QMenu" name="menuTextures">
<property name="title">
<string>Textures</string>
</property>
<addaction name="ActionConvertToDDS"/>
<addaction name="ActionConvertToTXTR"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuTextures"/>
</widget>
<action name="ActionSave">
<property name="icon">