我正在为移动设备开发一个基于OpenGL的虚拟纹理图书馆。
这是PageIndirectionTable,我想得到一些反馈的库组件之一:
#ifndef VTLIB_VT_PAGE_INDIRECTION_TABLE_HPP
#define VTLIB_VT_PAGE_INDIRECTION_TABLE_HPP
namespace vt
{
// ======================================================
// PageIndirectionTable:
// ======================================================
class PageIndirectionTable
{
public:
// Default constructor initializes the table texture.
// Might throw and exception if initialization fails.
PageIndirectionTable(const int * vtPagesX, const int * vtPagesY, int vtNumLevels);
// Frees the OpenGL texture handle.
virtual ~PageIndirectionTable();
// Bind the texture as current OpenGL state.
void bind(int texUnit = 0) const;
// Draws the indirection texture as a screen-space quadrilateral for debug visualization.
// Manually binding the texture is not necessary. 'overlayScale' controls the scale of the overlay quad. From 0 to 1.
void visualizeIndirectionTexture(const float overlayScale[2]) const;
// Update the indirection table texture. This is called whenever the page cache changes.
// Array size must be 'PageTable::TotalTablePages'. Must bind first with PageIndirectionTable::bind().
virtual void updateIndirectionTexture(const struct CacheEntry * const pages) = 0;
// Write every mip-level of the indirection table to image files.
// The file name/path should not include an extension. Each level will be named ( pathname + "_level_number" ).
virtual bool writeIndirectionTextureToFile(const std::string & pathname, bool recolor) const = 0;
protected:
// OpenGL texture handle:
GLuint indirectionTextureId;
// Num mip-levels in the Virtual Texture and per-level page counts:
int numLevels;
std::array<int, MaxVTMipLevels> numPagesX;
std::array<int, MaxVTMipLevels> numPagesY;
};
using PageIndirectionTablePtr = std::shared_ptr<PageIndirectionTable>;
// ======================================================
// PageIndirectionTableRgba8888:
// ======================================================
// Uses a RGBA 8:8:8:8 texture to store the page indirection table.
class PageIndirectionTableRgba8888 final
: public PageIndirectionTable, public NonCopyable
{
public:
PageIndirectionTableRgba8888(const int * vtPagesX, const int * vtPagesY, int vtNumLevels);
void updateIndirectionTexture(const struct CacheEntry * const pages) override;
bool writeIndirectionTextureToFile(const std::string & pathname, bool recolor) const override;
private:
void initTexture();
// Size of a RGBA 8:8:8:8 pixel.
struct TableEntry
{
uint8_t cachePageX; // R
uint8_t cachePageY; // G
uint8_t scaleHigh; // B
uint8_t scaleLow; // A
};
static_assert(sizeof(TableEntry) == 4, "Expected 4 bytes size!");
private:
int totalTableEntries;
// Pointers to the indirection texture levels of 'tableEntryPool':
std::array<TableEntry *, MaxVTMipLevels> tableLevels;
// Data store. 'tableLevels' are pointers to this array.
// This allows us to perform a single memory allocation.
std::unique_ptr<TableEntry[]> tableEntryPool;
};
// ======================================================
// PageIndirectionTableRgb565:
// ======================================================
// Uses a RGB 5:6:5 texture to store the page indirection table.
class PageIndirectionTableRgb565 final
: public PageIndirectionTable, public NonCopyable
{
public:
PageIndirectionTableRgb565(const int * vtPagesX, const int * vtPagesY, int vtNumLevels);
void updateIndirectionTexture(const struct CacheEntry * const pages) override;
bool writeIndirectionTextureToFile(const std::string & pathname, bool recolor) const override;
private:
void initTexture();
// Size of a RGB 5:6:5 pixel. Use bit shifting to manipulate the data.
using TableEntry = uint16_t;
static_assert(sizeof(TableEntry) == 2, "Expected 2 bytes size!");
private:
int log2VirtPagesWide;
int totalTableEntries;
// Pointers to the indirection texture levels of 'tableEntryPool':
std::array<TableEntry *, MaxVTMipLevels> tableLevels;
// Data store. 'tableLevels' are pointers to this array.
// This allows us to perform a single memory allocation.
std::unique_ptr<TableEntry[]> tableEntryPool;
};
// ======================================================
// Factory function. Creates based on startup param.
// ======================================================
// Creates a PageIndirectionTable instance based on the
// global indirection table format configuration parameter.
PageIndirectionTablePtr createIndirectionTable(const int * vtPagesX, const int * vtPagesY, int vtNumLevels);
} // namespace vt {}
#endif // VTLIB_VT_PAGE_INDIRECTION_TABLE_HPPnamespace vt
{
// Filtering is fixed for the indirection tables.
static constexpr GLenum indirectionTexMinFilter = GL_NEAREST_MIPMAP_NEAREST;
static constexpr GLenum indirectionTexMagFilter = GL_NEAREST;
static constexpr GLenum indirectionTexAddressing = GL_REPEAT;
// ======================================================
// PageIndirectionTable:
// ======================================================
PageIndirectionTable::PageIndirectionTable(const int * vtPagesX, const int * vtPagesY, const int vtNumLevels)
: indirectionTextureId(0)
, numLevels(vtNumLevels)
{
assert(numLevels > 0 && numLevels <= MaxVTMipLevels);
clearArray(numPagesX);
clearArray(numPagesY);
for (int l = 0; l < numLevels; ++l)
{
assert(vtPagesX[l] > 0);
assert(vtPagesY[l] > 0);
numPagesX[l] = vtPagesX[l];
numPagesY[l] = vtPagesY[l];
}
}
PageIndirectionTable::~PageIndirectionTable()
{
gl::delete2DTexture(indirectionTextureId);
}
void PageIndirectionTable::bind(const int texUnit) const
{
gl::use2DTexture(indirectionTextureId, texUnit);
}
void PageIndirectionTable::visualizeIndirectionTexture(const float overlayScale[2]) const
{
gl::useShaderProgram(getGlobalShaders().drawIndirectionTable.programId);
gl::setShaderProgramUniform(getGlobalShaders().drawIndirectionTable.unifNdcQuadScale, overlayScale, 2);
// Draw a quad with the texture applied to it:
gl::use2DTexture(indirectionTextureId);
gl::drawNdcQuadrilateral();
gl::use2DTexture(0);
gl::useShaderProgram(0);
}
// ======================================================
// PageIndirectionTableRgba8888:
// ======================================================
PageIndirectionTableRgba8888::PageIndirectionTableRgba8888(const int * vtPagesX, const int * vtPagesY, const int vtNumLevels)
: PageIndirectionTable(vtPagesX, vtPagesY, vtNumLevels)
, totalTableEntries(0)
{
clearArray(tableLevels);
initTexture();
vtLogComment("New PageIndirectionTable RGBA-8:8:8:8 instance created...");
}
void PageIndirectionTableRgba8888::initTexture()
{
assert(indirectionTextureId == 0 && "Duplicate initialization!");
vtLogComment("Initializing page indirection texture with " << numLevels << " levels...");
// Count total texture size, including all mip-levels:
totalTableEntries = 0;
for (int l = 0; l < numLevels; ++l)
{
totalTableEntries += numPagesX[l] * numPagesY[l];
}
tableEntryPool.reset(new TableEntry[totalTableEntries]);
// Set up the pointers:
totalTableEntries = 0;
for (int l = 0; l < numLevels; ++l)
{
tableLevels[l] = tableEntryPool.get() + totalTableEntries;
totalTableEntries += numPagesX[l] * numPagesY[l]; // Move to the next level
}
glGenTextures(1, &indirectionTextureId);
if (indirectionTextureId == 0)
{
vtFatalError("Failed to generate a non-zero GL texture id for the page indirection texture!");
}
gl::use2DTexture(indirectionTextureId);
// Create/set all the mip-levels:
for (int l = 0; l < numLevels; ++l)
{
assert(tableLevels[l] != nullptr);
// Default initialize the entries:
for (int e = 0; e < numPagesX[l] * numPagesY[l]; ++e)
{
TableEntry & entry = tableLevels[l][e];
entry.cachePageX = 0;
entry.cachePageY = 0;
const uint16_t scale = (numPagesX[0] * 16) >> l;
entry.scaleHigh = (scale & 0xFF);
entry.scaleLow = (scale >> 8);
}
glTexImage2D(GL_TEXTURE_2D, l, GL_RGBA, numPagesX[l], numPagesY[l], 0, GL_RGBA, GL_UNSIGNED_BYTE, tableLevels[l]);
vtLogComment("Allocated indirection tex level #" << l << ". Size: " << numPagesX[l] << "x" << numPagesY[l] << " pixels.");
#if VT_EXTRA_GL_ERROR_CHECKING
gl::checkGLErrors(__FILE__, __LINE__);
#endif // VT_EXTRA_GL_ERROR_CHECKING
}
// iOS specific: Set max level (would probably have to use glGenerateMipmap() otherwise...)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL_APPLE, numLevels - 1);
// Set addressing mode:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, indirectionTexAddressing);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, indirectionTexAddressing);
// Set filtering:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, indirectionTexMinFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, indirectionTexMagFilter);
gl::use2DTexture(0);
gl::checkGLErrors(__FILE__, __LINE__);
vtLogComment("Page indirection texture #" << indirectionTextureId << " created. Num entries: "
<< totalTableEntries << ". " << numLevels << " levels.");
}
void PageIndirectionTableRgba8888::updateIndirectionTexture(const CacheEntry * __restrict const pages)
{
assert(pages != nullptr);
// Texture must be already bound!
// GL_UNPACK_ALIGNMENT should ideally be set to 4.
assert(indirectionTextureId == gl::getCurrent2DTexture());
// Assemble the indirection table, one mip-level at a time,
// stating from the lowest resolution one:
for (int l = (numLevels - 1); l >= 0; --l)
{
// Write all pages in a level:
for (int p = 0; p < PageCacheMgr::TotalCachePages; ++p)
{
const CacheEntry & cacheEntry = pages[p];
if ((pageIdExtractMipLevel(cacheEntry.pageId) != l) || (cacheEntry.pageId == InvalidPageId))
{
continue;
}
const int x = pageIdExtractPageX(cacheEntry.pageId);
const int y = pageIdExtractPageY(cacheEntry.pageId);
const int index = (x + y * numPagesX[l]);
assert(index >= 0);
assert(index < (numPagesX[l] * numPagesY[l]));
TableEntry & entry = tableLevels[l][index];
entry.cachePageX = cacheEntry.cacheCoord.x;
entry.cachePageY = cacheEntry.cacheCoord.y;
const uint16_t scale = (numPagesX[0] * 16) >> l;
entry.scaleHigh = (scale & 0xFF);
entry.scaleLow = (scale >> 8);
}
// Upsample for next level:
if (l != 0)
{
uint32_t * __restrict src = reinterpret_cast<uint32_t *>(tableLevels[l]);
uint32_t * __restrict dest = reinterpret_cast<uint32_t *>(tableLevels[l - 1]);
const int srcW = numPagesX[l];
const int destW = numPagesX[l - 1];
const int destH = numPagesY[l - 1];
for (int y = 0; y < destH; ++y)
{
for (int x = 0; x < destW; ++x)
{
dest[x + y * destW] = src[(x >> 1) + (y >> 1) * srcW];
}
}
}
}
for (int l = 0; l < numLevels; ++l)
{
glTexImage2D(GL_TEXTURE_2D, l, GL_RGBA, numPagesX[l], numPagesY[l], 0, GL_RGBA, GL_UNSIGNED_BYTE, tableLevels[l]);
#if VT_EXTRA_GL_ERROR_CHECKING
gl::checkGLErrors(__FILE__, __LINE__);
#endif // VT_EXTRA_GL_ERROR_CHECKING
}
}
bool PageIndirectionTableRgba8888::writeIndirectionTextureToFile(const std::string & pathname, const bool recolor) const
{
int levelsWritten = 0;
std::string levelNameStr, result;
for (int l = 0; l < numLevels; ++l)
{
Pixel4b * __restrict pixels = reinterpret_cast<Pixel4b *>(tableLevels[l]);
// Reverse the bits in the image pixels to make them stand out.
// Most of the pixels would be very dark otherwise.
if (recolor)
{
const size_t numPixels = numPagesX[l] * numPagesY[l];
for (size_t p = 0; p < numPixels; ++p)
{
Pixel4b & pix = pixels[p];
pix.r = reverseByte(pix.r);
pix.g = reverseByte(pix.g);
// Mix alpha (the texture index) it with blue:
uint8_t b = reverseByte(pix.b);
uint8_t a = reverseByte(pix.a);
pix.b = clampByte(a + b);
pix.a = 0xFF;
}
}
levelNameStr = pathname + "_" + std::to_string(l) + ".tga";
if (tool::writeTgaImage(levelNameStr, numPagesX[l], numPagesY[l], 4, reinterpret_cast<uint8_t *>(pixels), true, &result))
{
vtLogComment(result);
levelsWritten++;
}
}
return levelsWritten == numLevels;
}
// ======================================================
// PageIndirectionTableRgb565:
// ======================================================
PageIndirectionTableRgb565::PageIndirectionTableRgb565(const int * vtPagesX, const int * vtPagesY, const int vtNumLevels)
: PageIndirectionTable(vtPagesX, vtPagesY, vtNumLevels)
, log2VirtPagesWide(static_cast<int>(std::log2(vtPagesX[0])))
, totalTableEntries(0)
{
clearArray(tableLevels);
initTexture();
vtLogComment("New PageIndirectionTable RGB-5:6:5 instance created. log2VirtPagesWide = " << log2VirtPagesWide);
}
void PageIndirectionTableRgb565::initTexture()
{
assert(indirectionTextureId == 0 && "Duplicate initialization!");
vtLogComment("Initializing page indirection texture with " << numLevels << " levels...");
// Count total texture size, including all mip-levels:
totalTableEntries = 0;
for (int l = 0; l < numLevels; ++l)
{
totalTableEntries += numPagesX[l] * numPagesY[l];
}
tableEntryPool.reset(new TableEntry[totalTableEntries]);
// Set up the pointers:
totalTableEntries = 0;
for (int l = 0; l < numLevels; ++l)
{
tableLevels[l] = tableEntryPool.get() + totalTableEntries;
totalTableEntries += numPagesX[l] * numPagesY[l]; // Move to the next level
}
glGenTextures(1, &indirectionTextureId);
if (indirectionTextureId == 0)
{
vtFatalError("Failed to generate a non-zero GL texture id for the page indirection texture!");
}
gl::use2DTexture(indirectionTextureId);
// Create/set all the mip-levels:
for (int l = 0; l < numLevels; ++l)
{
assert(tableLevels[l] != nullptr);
// Default initialize the entries:
for (int e = 0; e < numPagesX[l] * numPagesY[l]; ++e)
{
TableEntry & entry = tableLevels[l][e];
entry = 0;
entry = ((log2VirtPagesWide - l) << 5);
}
glTexImage2D(GL_TEXTURE_2D, l, GL_RGB, numPagesX[l], numPagesY[l], 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, tableLevels[l]);
vtLogComment("Allocated indirection tex level #" << l << ". Size: " << numPagesX[l] << "x" << numPagesY[l] << " pixels.");
#if VT_EXTRA_GL_ERROR_CHECKING
gl::checkGLErrors(__FILE__, __LINE__);
#endif // VT_EXTRA_GL_ERROR_CHECKING
}
// iOS specific: Set max level (would probably have to use glGenerateMipmap() otherwise...)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL_APPLE, numLevels - 1);
// Set addressing mode:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, indirectionTexAddressing);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, indirectionTexAddressing);
// Set filtering:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, indirectionTexMinFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, indirectionTexMagFilter);
gl::use2DTexture(0);
gl::checkGLErrors(__FILE__, __LINE__);
vtLogComment("Page indirection texture #" << indirectionTextureId << " created. Num entries: "
<< totalTableEntries << ". " << numLevels << " levels.");
}
void PageIndirectionTableRgb565::updateIndirectionTexture(const CacheEntry * __restrict const pages)
{
assert(pages != nullptr);
// Texture must be already bound!
// GL_UNPACK_ALIGNMENT should ideally be set to 4.
assert(indirectionTextureId == gl::getCurrent2DTexture());
// Assemble the indirection table, one mip-level at a time,
// stating from the lowest resolution one:
for (int l = (numLevels - 1); l >= 0; --l)
{
// Write all pages in a level:
for (int p = 0; p < PageCacheMgr::TotalCachePages; ++p)
{
const CacheEntry & cacheEntry = pages[p];
if ((pageIdExtractMipLevel(cacheEntry.pageId) != l) || (cacheEntry.pageId == InvalidPageId))
{
continue;
}
const int x = pageIdExtractPageX(cacheEntry.pageId);
const int y = pageIdExtractPageY(cacheEntry.pageId);
const int index = (x + y * numPagesX[l]);
assert(index >= 0);
assert(index < (numPagesX[l] * numPagesY[l]));
TableEntry & entry = tableLevels[l][index];
entry = ((cacheEntry.cacheCoord.x * 32 / PageTable::TableSizeInPages) << 11) |
((log2VirtPagesWide - l) << 5) | (cacheEntry.cacheCoord.y * 32 / PageTable::TableSizeInPages);
}
// Upsample for next level:
if (l != 0)
{
TableEntry * __restrict src = tableLevels[l];
TableEntry * __restrict dest = tableLevels[l - 1];
const int srcW = numPagesX[l];
const int destW = numPagesX[l - 1];
const int destH = numPagesY[l - 1];
for (int y = 0; y < destH; ++y)
{
for (int x = 0; x < destW; ++x)
{
dest[x + y * destW] = src[(x >> 1) + (y >> 1) * srcW];
}
}
}
}
for (int l = 0; l < numLevels; ++l)
{
glTexImage2D(GL_TEXTURE_2D, l, GL_RGB, numPagesX[l], numPagesY[l], 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, tableLevels[l]);
#if VT_EXTRA_GL_ERROR_CHECKING
gl::checkGLErrors(__FILE__, __LINE__);
#endif // VT_EXTRA_GL_ERROR_CHECKING
}
}
bool PageIndirectionTableRgb565::writeIndirectionTextureToFile(const std::string & pathname, const bool recolor) const
{
int levelsWritten = 0;
std::string levelNameStr, result;
std::vector<Pixel4b> tempImage(numPagesX[0] * numPagesY[0]);
// Convert the 5:6:5 texture to 8bits RGBA first, to make writing the image simpler.
auto makeRGBA = [&tempImage, recolor](const TableEntry * data, size_t numPixels) -> Pixel4b *
{
for (size_t p = 0; p < numPixels; ++p)
{
TableEntry src = data[p];
Pixel4b & dest = tempImage[p];
// Unpack RGB 565 to RGBA fixing alpha to 255:
dest.r = ((src & 0x7800) >> 11);
dest.g = ((src & 0x07E0) >> 5);
dest.b = ((src & 0x001F) >> 0);
dest.a = 0xFF;
// Reverse the bits in the image pixels to make them stand out.
// Most of the pixels would be very dark otherwise.
if (recolor)
{
dest.r = reverseByte(dest.r);
dest.g = reverseByte(dest.g);
dest.b = reverseByte(dest.b);
}
}
return tempImage.data();
};
for (int l = 0; l < numLevels; ++l)
{
const size_t numPixels = numPagesX[l] * numPagesY[l];
const Pixel4b * pixels = makeRGBA(tableLevels[l], numPixels);
levelNameStr = pathname + "_" + std::to_string(l) + ".tga";
if (tool::writeTgaImage(levelNameStr, numPagesX[l], numPagesY[l], 4, reinterpret_cast<const uint8_t *>(pixels), true, &result))
{
vtLogComment(result);
levelsWritten++;
}
}
return levelsWritten == numLevels;
}
// ======================================================
// createIndirectionTable():
// ======================================================
PageIndirectionTablePtr createIndirectionTable(const int * vtPagesX, const int * vtPagesY, const int vtNumLevels)
{
extern IndirectionTableFormat getIndirectionTableFormat() noexcept;
switch (getIndirectionTableFormat())
{
case IndirectionTableFormat::Rgb565 :
return std::make_shared<PageIndirectionTableRgb565>(vtPagesX, vtPagesY, vtNumLevels);
case IndirectionTableFormat::Rgba8888 :
return std::make_shared<PageIndirectionTableRgba8888>(vtPagesX, vtPagesY, vtNumLevels);
default :
vtFatalError("Invalid IndirectionTableFormat!");
} // switch (getIndirectionTableFormat())
}
} // namespace vt {}我主要关注的是,PageIndirectionTable的两个实现共享相当数量的相似代码(复制粘贴过多),因此我不确定是将一些代码合并到辅助方法中,从而增加复杂性,还是保持原样,避免进一步抽象它。对此有什么建议吗?
其他的建议和批评总是受欢迎的。
发布于 2014-12-28 16:14:17
一般来说,这段代码写得很好.做得好!
小改进:
std::uint8_t而不是uint8_t。前者是C++11,后者是C99。std::uint16_t和其他固定宽度的整数也是一样的.NonCopyable并显式删除副本构造函数。。struct中删除const struct CacheEntry * const pages关键字。它在C++中是不需要的。static constexpr GLenum indirectionTexMinFilter = GL_NEAREST_MIPMAP_NEAREST;和类似的定义。numLevels为const。据我所见,您只写入构造函数的成员初始化程序列表中的numLevels。将numLevels声明为const将避免意外写入和澄清意图。挑剔的改进:
for (int i = 0; end != i; ++i)是const或r值时,养成编写end而不是for (int i = 0; i != end; ++i)的习惯。这样,如果您不小心在循环中写入end = i (赋值而不是测试),您将得到一个编译器错误。<=,>=,特别是==也是如此。我甚至更喜欢那种风格,只为<和>,只是为了保持一致。这适用于所有原始的for循环。另外,你考虑过通过CRTP实现静态多态性吗?在没有看到客户端代码的情况下,我不能说这是否适用。如果它确实适用,那么您可以通过删除vtable获得一些性能。然而,现实世界的影响可能微乎其微。
https://codereview.stackexchange.com/questions/71111
复制相似问题