Ajout de changements de timo + augmentation de performance de 10%

This commit is contained in:
Timothée Leclaire-Fournier 2024-01-21 00:48:24 -05:00
parent d62603b9c3
commit 93d5cf1c29
4 changed files with 276 additions and 145 deletions

View File

@ -1,4 +1,4 @@
#include "GOLTeamH.h" #include "GOLTeamH.h"
@ -42,11 +42,11 @@ GOL::Statistics GOLTeamH::statistics() const
.width = width(), .width = width(),
.height = height(), .height = height(),
.totalCells = size(), .totalCells = size(),
.iteration = mIteration, .iteration = mIteration
.totalDeadAbs = mData.totalDead(), //.totalDeadAbs = mData.totalDead(),
.totalAliveAbs = mData.totalAlive(), //.totalAliveAbs = mData.totalAlive(),
.totalDeadRel = mData.totalDeadRel(), //.totalDeadRel = mData.totalDeadRel(),
.totalAliveRel = mData.totalAliveRel() //.totalAliveRel = mData.totalAliveRel()
// .tendencyAbs = ..., // .tendencyAbs = ...,
// .tendencyRel = ... // .tendencyRel = ...
}); });
@ -146,11 +146,11 @@ bool GOLTeamH::setRule(std::string const& rule)
{ {
mRule = rule; mRule = rule;
bool firstPart{ true }; bool firstPart{ true };
std::bitset<9> parsedRuleRevive, parsedRuleSurvive; uint16_t parsedRuleRevive{}, parsedRuleSurvive{};
// On vérifie que la chaine de charactères contient un B au début. // On vérifie que la chaine de charactères contient un B au début.
// 5 = taille minimale // 5 = taille minimale
if (rule.size() < 5 || !(rule[0] == 'B' || rule[0] == 'b')) if (rule.size() < 3 || !(rule[0] == 'B' || rule[0] == 'b'))
return false; return false;
for (size_t i{ 1 }; i < rule.length(); i++) { for (size_t i{ 1 }; i < rule.length(); i++) {
@ -160,9 +160,9 @@ bool GOLTeamH::setRule(std::string const& rule)
// Si c'est un chiffre, on continue en enregistrant la valeur. // Si c'est un chiffre, on continue en enregistrant la valeur.
if (opt.has_value()) { if (opt.has_value()) {
if (firstPart) if (firstPart)
parsedRuleRevive.set(opt.value()); parsedRuleRevive |= 1u << opt.value();
else else
parsedRuleSurvive.set(opt.value()); parsedRuleSurvive |= 1u << opt.value();
continue; continue;
} }
@ -179,14 +179,9 @@ bool GOLTeamH::setRule(std::string const& rule)
return false; return false;
} }
// Si les les deux règles ont au moins un nombre, alors OK. mParsedRuleRevive = parsedRuleRevive;
if (parsedRuleRevive.any() && parsedRuleSurvive.any()) { mParsedRuleSurvive = parsedRuleSurvive;
mParsedRuleRevive = parsedRuleRevive; return true;
mParsedRuleSurvive = parsedRuleSurvive;
return true;
}
else
return false;
} }
//! \brief Mutateur modifiant la stratégie de gestion de bord. //! \brief Mutateur modifiant la stratégie de gestion de bord.
@ -219,31 +214,23 @@ bool GOLTeamH::setRule(std::string const& rule)
//! **grille**. //! **grille**.
void GOLTeamH::setBorderManagement(BorderManagement borderManagement) void GOLTeamH::setBorderManagement(BorderManagement borderManagement)
{ {
mBorderManagement = borderManagement; // On assigne la nouvelle stratégie mBorderManagement = borderManagement;
// Logique spécifique à chaque stratégie de gestion de bord mIteration = 0;
switch (borderManagement)
{
case BorderManagement::immutableAsIs:
switch (borderManagement) {
case GOL::BorderManagement::foreverDead:
mData.fillBorder(GridTeamH::CellType::dead);
break; break;
case GOL::BorderManagement::foreverAlive:
case BorderManagement::foreverDead: mData.fillBorder(GridTeamH::CellType::alive);
mData.setBorderValue(State::dead);
break; break;
case GOL::BorderManagement::warping:
case BorderManagement::foreverAlive: mData.fillBorderWarped();
mData.setBorderValue(State::alive);
break; break;
case GOL::BorderManagement::mirror:
case BorderManagement::warping: mData.fillBorderMirror();
break;
case BorderManagement::mirror:
break; break;
} }
mIteration = 0;
} }
@ -272,7 +259,7 @@ void GOLTeamH::setState(int x, int y, State state)
//! \param state L'état d'initialisation des cellules. //! \param state L'état d'initialisation des cellules.
void GOLTeamH::fill(State state) void GOLTeamH::fill(State state)
{ {
mData.fill(state); mData.fill(state, mBorderManagement == GOL::BorderManagement::immutableAsIs);
mIteration = 0; mIteration = 0;
} }
@ -288,7 +275,7 @@ void GOLTeamH::fill(State state)
//! \param firstCell L'état de la première cellule. //! \param firstCell L'état de la première cellule.
void GOLTeamH::fillAlternately(State firstCell) void GOLTeamH::fillAlternately(State firstCell)
{ {
mData.fillAternately(firstCell); mData.fillAternately(firstCell, mBorderManagement == GOL::BorderManagement::immutableAsIs);
mIteration = 0; mIteration = 0;
} }
@ -305,7 +292,7 @@ void GOLTeamH::fillAlternately(State firstCell)
//! vivante. La valeur doit être comprise entre 0.0 et 1.0 inclusivement. //! vivante. La valeur doit être comprise entre 0.0 et 1.0 inclusivement.
void GOLTeamH::randomize(double percentAlive) void GOLTeamH::randomize(double percentAlive)
{ {
mData.randomize(percentAlive); mData.randomize(percentAlive, mBorderManagement == GOL::BorderManagement::immutableAsIs);
mIteration = 0; mIteration = 0;
} }
@ -327,6 +314,8 @@ void GOLTeamH::randomize(double percentAlive)
//! \param centerY La coordonnée en y de la grille où se trouve centré le patron. //! \param centerY La coordonnée en y de la grille où se trouve centré le patron.
//! \return true si le patron est valide, false sinon. //! \return true si le patron est valide, false sinon.
//! //!
// TODO
bool GOLTeamH::setFromPattern(std::string const& pattern, int centerX, int centerY) bool GOLTeamH::setFromPattern(std::string const& pattern, int centerX, int centerY)
{ {
mIteration = 0; mIteration = 0;
@ -343,6 +332,8 @@ bool GOLTeamH::setFromPattern(std::string const& pattern, int centerX, int cente
//! //!
//! \param pattern Le patron à appliquer. //! \param pattern Le patron à appliquer.
//! \return true si le patron est valide, false sinon. //! \return true si le patron est valide, false sinon.
// TODO
bool GOLTeamH::setFromPattern(std::string const& pattern) bool GOLTeamH::setFromPattern(std::string const& pattern)
{ {
mIteration = 0; mIteration = 0;
@ -383,14 +374,71 @@ void GOLTeamH::setSolidColor(State state, Color const& color)
//! nouvel état de chaque cellule suivant l'état précédent. Les statistiques //! nouvel état de chaque cellule suivant l'état précédent. Les statistiques
//! doivent tenir compte de cette évolution. //! doivent tenir compte de cette évolution.
// TODO: performance
void GOLTeamH::processOneStep() void GOLTeamH::processOneStep()
{ {
// On commence à itérer sur les côtés. En règlant ces cas particuliers, on // Pour des raisons de performance, on accède à la grille interne en faisant
// peut éviter des branches dans la boucle principale. Le branch predictor // des calculs pour le border manuellement.
// aime cela. auto& grid{ mData.data() };
for (size_t i{}; i < mData.width(); ++i) { auto& intGrid{ mData.intData() };
if (mBorderManagement == GOL::BorderManagement::foreverDead) {
size_t aliveCount{};
size_t offset{ width() + 2 };
auto* ptrGrid{ &grid[0] }; // Pointeur qui se promène en mémoire.
auto* const ptrGridInt{ &intGrid[0] }; // Pointeur statique pour des calculs.
for (size_t i{}; i < mData.realSize() - offset - 1; ++i) {
if (mData.isInBorder(i)) {
ptrGrid++;
continue;
}
aliveCount = 0;
// On prend avantage du fait que GOL::State::alive = 1 et donc
// on retire des branches if.
// Top
ptrGrid -= offset + 1;
aliveCount += static_cast<uint8_t> (*ptrGrid);
ptrGrid++;
aliveCount += static_cast<uint8_t> (*ptrGrid);
ptrGrid++;
aliveCount += static_cast<uint8_t> (*ptrGrid);
// Milieu
ptrGrid += offset - 2;
aliveCount += static_cast<uint8_t> (*ptrGrid);
ptrGrid += 2;
aliveCount += static_cast<uint8_t> (*ptrGrid);
// Dessous
ptrGrid += offset - 2;
aliveCount += static_cast<uint8_t> (*ptrGrid);
ptrGrid++;
aliveCount += static_cast<uint8_t> (*ptrGrid);
ptrGrid++;
aliveCount += static_cast<uint8_t> (*ptrGrid);
// On retourne à une place plus loin qu'à l'origine.
ptrGrid -= offset;
// On prend avantage du fait que GOL::State::alive = 1.
//
// On évite aussi d'utiliser l'opérateur []. En profilant, nous avons vu un
// impact de performance de ~5%.
if (*(ptrGrid - 1) == GOL::State::alive)
*(ptrGridInt + i) = static_cast<GOL::State>(static_cast<bool>(mParsedRuleSurvive & (1u << aliveCount)));
else
*(ptrGridInt + i) = static_cast<GOL::State>(static_cast<bool>(mParsedRuleRevive & (1u << aliveCount)));
}
ptrGrid = nullptr;
mData.switchToIntermediate();
mIteration.value()++;
} }
} }
@ -436,29 +484,30 @@ void GOLTeamH::updateImage(uint32_t* buffer, size_t buffer_size) const
auto s_ptr = buffer; auto s_ptr = buffer;
auto e_ptr = &buffer[buffer_size]; auto e_ptr = &buffer[buffer_size];
auto& grid = mData.data();
// On itère sur chaque éléments du tableau et on associe la couleur. // On itère sur chaque éléments du tableau et on associe la couleur.
for (const auto& i : mData.data()) { for (size_t index{ width() + 2 }; // On ignore la ligne du bas.
if (i == GridTeamH::CellType::alive) { index < (width() + 2) * (height() + 1); // On ignore la ligne du haut.
*s_ptr &= 0; // Clear index++) {
*s_ptr |= MAX_ALPHA << 24; // Alpha = 255
*s_ptr |= mAliveColor.red << 16; if (mData.isInBorder(index))
*s_ptr |= mAliveColor.green << 8; continue;
*s_ptr |= mAliveColor.blue;
} auto var = static_cast<uint8_t>(grid[index]);
else {
*s_ptr &= 0; *s_ptr &= 0; // Clear
*s_ptr |= MAX_ALPHA << 24; *s_ptr |= MAX_ALPHA << 24; // Alpha = 255
*s_ptr |= mDeadColor.red << 16;
*s_ptr |= mDeadColor.green << 8; *s_ptr |= mAliveColor.red * var << 16;
*s_ptr |= mDeadColor.blue; *s_ptr |= mAliveColor.green * var << 8;
} *s_ptr |= mAliveColor.blue * var;
*s_ptr |= mDeadColor.red * (1 - var) << 16;
*s_ptr |= mDeadColor.green * (1 - var) << 8;
*s_ptr |= mDeadColor.blue * (1 - var);
s_ptr++; s_ptr++;
// Sanity check, pour éviter des problèmes
if (s_ptr >= e_ptr)
break;
} }
} }

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <string> #include <string>
#include <array> #include <array>
#include <optional> #include <optional>
@ -11,14 +12,13 @@ constexpr unsigned char MAX_ALPHA = 255;
class GOLTeamH : public GOL class GOLTeamH : public GOL
{ {
public: public:
size_t width() const override { return mData.width(); } size_t width() const override;
size_t height() const override { return mData.height(); } size_t height() const override;
size_t size() const override { return mData.size(); } size_t size() const override;
State state(int x, int y) const override { return mData.value(x, y); } //details L'état d'une cellule peut être mort ou vivant. State state(int x, int y) const override;
std::string rule() const override { return mRule.value_or(std::move(std::string())); } //details La règle est une chaîne de caractères qui contient les règles de survie et de naissance. std::string rule() const override;
BorderManagement borderManagement() const override { return mBorderManagement.value_or(GOL::BorderManagement::immutableAsIs);} //details La gestion des bords est une énumération qui contient les différents types de gestion des bords. BorderManagement borderManagement() const override;
Color color(State state) const override { return state == GOL::State::alive ? mAliveColor : mDeadColor; } Color color(State state) const override;
//Accesseur dans le .cpp
Statistics statistics() const override; Statistics statistics() const override;
ImplementationInformation information() const override; ImplementationInformation information() const override;
@ -41,10 +41,12 @@ private:
std::optional<IterationType> mIteration; std::optional<IterationType> mIteration;
// On utilise un bitset qui contient les règles de chaque nombre. // On utilise un bitset qui contient les règles de chaque nombre.
std::bitset<9> mParsedRuleRevive, mParsedRuleSurvive; // On n'utilise pas std::bitset pour des raisons de performance.
uint16_t mParsedRuleRevive, mParsedRuleSurvive;
GridTeamH mData; GridTeamH mData;
Color mDeadColor, mAliveColor; Color mDeadColor, mAliveColor;
// Fonction utilisée à l'interne. // Fonctions utilisées à l'interne.
std::optional<unsigned char> convertCharToNumber(const char c); std::optional<unsigned char> convertCharToNumber(const char c);
}; };

View File

@ -1,10 +1,8 @@
#include "GridTeamH.h" #include "GridTeamH.h"
#include "GOL.h" #include "GOL.h"
#include <iostream> #include <iostream>
//constructeur Grid par défaut // Constructeur Grid par défaut
GridTeamH::GridTeamH() GridTeamH::GridTeamH()
: GridTeamH(100, 100, CellType::alive) : GridTeamH(100, 100, CellType::alive)
{ {
@ -22,54 +20,50 @@ GridTeamH::~GridTeamH()
} }
//Inlining de ces assesseur dans GridTeam.h
// Accesseur retournant la largeur de la grille.
// Accesseur retournant la hauteur de la grille.
// Accesseur retournant le nombre de cellule de la grille (la taille de la grille).
// Mutateur modifiant la taille de la grille et initialise le contenu par la valeur spécifiée. // Mutateur modifiant la taille de la grille et initialise le contenu par la valeur spécifiée.
void GridTeamH::resize(size_t width, size_t height, CellType initValue) void GridTeamH::resize(size_t width, size_t height, CellType initValue)
{ {
// TODO: Performance de resize avec beaucoup d'appel? mData.resize((width + 2) * (height + 2));
// Investiguer reserve + resize mIntermediateData.resize((width + 2) * (width + 2));
mData.resize(width * height);
mWidth = width; mWidth = width;
mHeight = height; mHeight = height;
fill(initValue); fill(initValue, false);
} }
// Accesseur retournant la valeur d'une cellule à une certaine coordonnée. // Accesseur retournant la valeur d'une cellule à une certaine coordonnée.
GridTeamH::CellType GridTeamH::value(int column, int row) const GridTeamH::CellType GridTeamH::value(int column, int row) const
{ {
return mData[(column - 1) * (row - 1)]; size_t offset{ mWidth + 2 };
return mData[offset * row + (column - 1)];
} }
// Mutateur modifiant la valeur d'une cellule à une certaine coordonnée. // Mutateur modifiant la valeur d'une cellule à une certaine coordonnée.
void GridTeamH::setValue(int column, int row, CellType value) void GridTeamH::setValue(int column, int row, CellType value)
{ {
mData[(column - 1) * (row - 1)] = value; size_t offset{ mWidth + 2 };
mData[offset * row + (column - 1)] = value;
} }
// Accesseur retournant la valeur d'une cellule à une certaine coordonnée. // Accesseur retournant la valeur d'une cellule à une certaine coordonnée.
std::optional<GridTeamH::CellType> GridTeamH::at(int column, int row) const std::optional<GridTeamH::CellType> GridTeamH::at(int column, int row) const
{ {
if (column > mWidth || row > mHeight) if (column >= mWidth || row >= mHeight)
return std::nullopt; return std::nullopt;
return mData[(column - 1) * (row - 1)]; size_t offset{ mWidth + 2 };
return mData[offset * row + (column - 1)];
} }
// Mutateur modifiant la valeur d'une cellule à une certaine coordonn<EFBFBD>e. // Mutateur modifiant la valeur d'une cellule à une certaine coordonn e.
void GridTeamH::setAt(int column, int row, CellType value) void GridTeamH::setAt(int column, int row, CellType value)
{ {
if (column > mWidth || row > mHeight) if (column > mWidth || row > mHeight)
return; return;
mData[(column - 1) * (row - 1)] = value; size_t offset{ mWidth + 2 };
mData[offset * row + (column - 1)] = value;
} }
@ -79,6 +73,23 @@ GridTeamH::DataType const& GridTeamH::data() const
return mData; return mData;
} }
// Accesseur en lecture/écriture sur le "buffer" de la grille.
GridTeamH::DataType& GridTeamH::data()
{
return mData;
}
GridTeamH::DataType const& GridTeamH::intData() const
{
return mIntermediateData;
}
GridTeamH::DataType& GridTeamH::intData()
{
return mIntermediateData;
}
// TODO: FIX
// https://en.cppreference.com/w/cpp/algorithm/count // https://en.cppreference.com/w/cpp/algorithm/count
size_t GridTeamH::totalDead() const size_t GridTeamH::totalDead() const
{ {
@ -90,6 +101,7 @@ float GridTeamH::totalDeadRel() const
return static_cast<float>(totalDead()) / static_cast<float>(size()); return static_cast<float>(totalDead()) / static_cast<float>(size());
} }
// TODO: FIX
size_t GridTeamH::totalAlive() const size_t GridTeamH::totalAlive() const
{ {
return std::count_if(mData.begin(), mData.end(), [](auto& i) { return i == CellType::alive; }); return std::count_if(mData.begin(), mData.end(), [](auto& i) { return i == CellType::alive; });
@ -100,55 +112,90 @@ float GridTeamH::totalAliveRel() const
return static_cast<float>(totalAlive()) / static_cast<float>(size()); return static_cast<float>(totalAlive()) / static_cast<float>(size());
} }
void GridTeamH::fill(CellType value) void GridTeamH::fill(CellType value, bool fillBorder)
{ {
for (auto& i : mData) if (fillBorder) {
i = value; for (auto& i : mData)
i = value;
}
else {
for (size_t index{}; index < mData.size(); index++) {
if (isInBorder(index))
continue;
mData[index] = value;
}
}
} }
void GridTeamH::fillAternately(CellType initValue) // TODO: FIX LES COLONNES EN FAISANT ATTENTION AU BORDER
// TIMOTHEE: Je m'en occupe.
void GridTeamH::fillAternately(CellType initValue, bool fillBorder)
{ {
// Détermine la valeur opposée à initValue
auto otherValue = (initValue == CellType::alive) ? CellType::dead : CellType::alive; auto otherValue = (initValue == CellType::alive) ? CellType::dead : CellType::alive;
// Boucle pour parcourir chaque ligne de la grille if (fillBorder) {
for (size_t row = 0; row < mHeight; row++) { for (size_t i{}; i < mData.size(); i++)
// Boucle pour parcourir chaque colonne de la grille mData[i] = !(i % 2) ? initValue : otherValue;
for (size_t col = 0; col < mWidth; col++) { }
// Calcul de l'index dans la grille bidimensionnelle else {
size_t index = row * mWidth + col; for (size_t index{}; index < mData.size(); index++) {
if (isInBorder(index))
continue;
// Alternance de la valeur pour chaque cellule en fonction de la ligne et de la colonne mData[index] = !(index % 2) ? initValue : otherValue;
mData[index] = ((row + col) % 2 == 0) ? initValue : otherValue;
} }
} }
} }
void GridTeamH::randomize(double percentAlive) void GridTeamH::randomize(double percentAlive, bool fillBorder)
{ {
for (auto& i : mData) { if (fillBorder) {
if (mDistribution(mEngine) < percentAlive) for (auto& i : mData) {
i = CellType::alive; if (mDistribution(mEngine) < percentAlive)
else i = CellType::alive;
i = CellType::dead; else
i = CellType::dead;
}
} }
} else {
for (size_t index{}; index < mData.size() - 1; index++) {
if (isInBorder(index))
continue;
//Fonction qui attribue CellType aux case de la grille présent en bordure if (mDistribution(mEngine) < percentAlive)
//sans changer les autres case mData[index] = CellType::alive;
void GridTeamH::setBorderValue(CellType initValue) else
{ mData[index] = CellType::dead;
// Boucle pour parcourir chaque ligne de la grille
for (size_t row = 0; row < mHeight; row++) {
// Boucle pour parcourir chaque colonne de la grille
for (size_t col = 0; col < mWidth; col++) {
// Calcul de l'index dans la grille bidimensionnelle
size_t index = row * mWidth + col;
// Alternance de la valeur pour chaque cellule en fonction de la ligne et de la colonne
if (row == 0 || row == mHeight - 1 || col == 0 || col == mWidth - 1)
mData[index] = initValue;
} }
} }
} }
// Performance non nécessaire.
void GridTeamH::fillBorder(CellType value)
{
for (size_t index{}; index < mData.size(); index++) {
if (isInBorder(index))
mData[index] = value;
}
}
// TODO
void GridTeamH::fillBorderWarped()
{
}
// TODO
void GridTeamH::fillBorderMirror()
{
}
void GridTeamH::switchToIntermediate()
{
// Swap pour la performance.
mData.swap(mIntermediateData);
}

View File

@ -1,10 +1,20 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include <random> #include <random>
#include "GOL.h" #include "GOL.h"
/*
Cette classe fonctionne en ayant deux std::vector qui contiennent les statuts
des cellules. Un vecteur intermédiaire est échangé avec .swap() avec le vecteur
réel lors des calculs.
On utilise une grandeur de width + 2 par height + 2. Cela nous permet d'avoir un
contour qui sera modifié de façon dynamique par la règle du border. Cela est
complètement transparent aux fonctions publiques autres que certaines fonctions
spécialisées.
*/
class GridTeamH class GridTeamH
{ {
@ -23,9 +33,11 @@ public:
~GridTeamH(); ~GridTeamH();
// Accesseurs et mutateurs de la grille // Accesseurs et mutateurs de la grille
size_t width() const {return mWidth; } // inline puisque trivial.
size_t width() const { return mWidth; }
size_t height() const { return mHeight; } size_t height() const { return mHeight; }
size_t size() const { return mWidth * mHeight; } size_t size() const { return mHeight * mWidth; }
size_t realSize() const { return (mHeight + 2) * (mWidth + 2); }
void resize(size_t width, size_t height, CellType initValue = CellType{}); void resize(size_t width, size_t height, CellType initValue = CellType{});
@ -38,6 +50,10 @@ public:
// Accesseurs du "buffer" de la grille // Accesseurs du "buffer" de la grille
DataType const& data() const; DataType const& data() const;
DataType& data();
DataType const& intData() const;
DataType& intData();
size_t totalDead() const; size_t totalDead() const;
float totalDeadRel() const; float totalDeadRel() const;
@ -45,16 +61,33 @@ public:
size_t totalAlive() const; size_t totalAlive() const;
float totalAliveRel() const; float totalAliveRel() const;
void fill(CellType value); void fill(CellType value, bool fillBorder);
void fillAternately(CellType initValue); void fillAternately(CellType initValue, bool fillBorder);
void randomize(double percentAlive); void randomize(double percentAlive, bool fillBorder);
void setBorderValue(CellType value); void fillBorder(CellType value);
void fillBorderWarped();
void fillBorderMirror();
void switchToIntermediate();
bool isInBorder(size_t index) const;
private: private:
DataType mData; DataType mData, mIntermediateData;
size_t mWidth, mHeight; //hauteur et largeur de la grid size_t mWidth, mHeight;
// Pour la génération de nombres aléatoires
std::random_device mRandomDevice; std::random_device mRandomDevice;
std::mt19937 mEngine; std::mt19937 mEngine;
std::uniform_real_distribution<> mDistribution; std::uniform_real_distribution<> mDistribution;
}; };
// Attention: performance terrible si utilisation. Seulement lorsque vitesse
// n'est pas demandée.
inline bool GridTeamH::isInBorder(size_t index) const
{
return(index % (mWidth + 2) < 1
|| index % (mWidth + 2) > mWidth
|| index < mWidth + 2
|| index >(mWidth + 2) * (mHeight + 1));
}